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..7ed4d47f21d29994d1b36915fde047dd002a7b78 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.huawei.hmos.world", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ 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..53b062fc6363d383e0923cacadd18f4d7c361210 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "HMOSWorldClient_New" + } + ] +} diff --git a/AppScope/resources/base/media/app_icon.png b/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..70d199582d58f8a5d6fb3ecacf7f8c55b93f14b1 Binary files /dev/null and b/AppScope/resources/base/media/app_icon.png differ 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..be1030266c9f5a7d24a65536b93e404c53c11b58 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,138 @@ -# HMOSWorld_SamplesCollection +# HMOS世界 - 示例代码合集 +## 一、「HMOS 世界 - 示例代码合集」APP 尝鲜上线了! +为了帮助广大开发者更高效的完成鸿蒙应用开发,华为官方重磅打造了一款名为「HMOS世界 - 示例代码合集」的开源APP。「HMOS世界」汇聚官网优质代码案例,覆盖多场景开发需求,通过标准化、模块化的代码实践,帮助开发者快速掌握鸿蒙应用开发技巧,加速项目落地进程,开启鸿蒙开发新征程! -#### 介绍 -「HMOS世界」汇聚官网优质代码案例,覆盖多场景开发需求,通过标准化、模块化的代码实践,帮助开发者快速掌握鸿蒙应用开发技巧,加速项目落地进程,开启鸿蒙开发新征程! +【应用开发最佳实践】 -#### 软件架构 -软件架构说明 +HMOS世界承载鸿蒙应用架构最佳实践,支持1+8设备运行,全方位体现鸿蒙应用的精致、流畅、智能、易用、安全、全场景互联等特点,并持续迭代鸿蒙新特性。 +【示例代码一键获取】 -#### 安装教程 +其中内置集成数百个Samples示例代码,覆盖高频的鸿蒙应用开发场景,并支持源码的一键分享,给开发者提供所见即所得的样例代码,支撑开发者高效完成鸿蒙应用的开发。 -1. xxxx -2. xxxx -3. xxxx +【代码开源地址】 -#### 使用说明 +- HMOS世界源码地址:https://gitee.com/harmonyos_samples/hmosworld_samplescollection +- 示例代码汇总地址:https://gitee.com/harmonyos_samples -1. xxxx -2. xxxx -3. xxxx +【效果图】 +![image](hmosword-build/image/9.png) -#### 参与贡献 +@联系我们 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +HMOS世界相关开发案例正在陆续更新中,您还期待我们为您提供什么样的开发案例,或还有什么宝贵意见,欢迎联系我们,非常期待您的反馈和建议,以促进我们不断改进! +- 官方邮箱:hmosworld@huawei.com +## 二、功能介绍(手机、折叠屏、平板、PC/2in1) +### 组件库首页 +组件首页作为APP的入口页面,负责展示组件卡片,给用户提供不同组件的展示入口。主要包括页签区和内容区,内容区包括banner海报位与卡片入口。 -#### 特技 +| 手机 | 折叠屏(展开态) | 平板 | +|------------------------|---------------------------------------|---------------------------------------| +| ![](hmosword-build/screenshots/1.png) | ![](hmosword-build/screenshots/2.png) | ![](hmosword-build/screenshots/3.png) | -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/) +### 组件库详情页 +组件详情页承载全套符合鸿蒙设计规范的ArkUI组件和开箱即用的特性能力。页面包括预览区、属性调整区、代码区和推荐列表四块,手动调整属性,可以看到预览区与代码区会发生相应的变化。 + +| 手机 | 折叠屏(展开态) | 平板 | +|----------------------------------------|----------------------------------------|----------------------------------------| +| ![](hmosword-build/screenshots/10.png) | ![](hmosword-build/screenshots/11.png) | ![](hmosword-build/screenshots/12.png) | +### 样例页 +样例页面主要由banner海报位与Samples卡片入口组成,入口位包含四个tab页签:2025 HDC、多设备开发、ArkUI实践、功能开发。点击不同页签展示不同类别的Sample案例。 + +| 手机 | 折叠屏(展开态) | 平板 | +|---------------------------------------|---------------------------------------|---------------------------------------| +| ![](hmosword-build/screenshots/4.png) | ![](hmosword-build/screenshots/5.png) | ![](hmosword-build/screenshots/6.png) | +### 实践 +实践页面主要由banner海报位与最佳实践文章卡片入口组成。文章围绕“如何构建一个鸿蒙大型应用”,将HMOS世界设计、开发到上架整个开发者旅程以最佳实践文章的形式进行内容输出。 + +| 手机 | 折叠屏(展开态) | 平板 | +|---------------------------------------|---------------------------------------|---------------------------------------| +| ![](hmosword-build/screenshots/7.png) | ![](hmosword-build/screenshots/8.png) | ![](hmosword-build/screenshots/9.png) | +## 三、功能介绍(华为智能穿戴设备) +### 样例页 +HMOS世界在样例模块已集成四个案例:包括音乐播放、视频播放、地图导航和骑行导航等。 + +## 四、工程结构 +``` +├──common/src/main/ets // 公共模块 +│ ├──component // 公共组件库 +│ ├──constant // 公共常量文件 +│ ├──model // 公共数据类 +│ ├──routermanager // 路由管理类 +│ ├──storagemanager // 存储模块 +│ ├──updateservice // 包更新模块 +│ ├──util // 工具类 +│ ├──view // 公共页面库 +│ └──viewmodel // ViewModel父类 +├──features // feature层 +│ ├──commonbusiness // feature公共模块 +│ ├──componentlibrary // 组件模块业务 +│ ├──devpractices // 样例模块 +│ ├──exploration // 实践模块 +│ └──mine // 我的模块 +├──products // 产品定制层 +│ ├──phone // 手机设备入口 +│ └──wearable // 华为智能穿戴设备入口 +└──hmosword-build // Sample下载脚本 +``` + +## 五、代码运行 +《HMOS世界 - 示例代码合集》APP集成了大量Sample,开发者可以选择: +1. 直接运行HMOS世界本体代码,体验组件与实践功能,样例模块Sample无法进入。 +2. 运行脚本下载Sample代码,体验全量HMOS世界功能。 + +### 1、直接运行HMOS世界 + +#### 手机、折叠屏、平板、PC/2in1设备运行 +1. DevEco studio打开工程文件,等待Sync完成。 +2. 点击运行入口配置,选中phone。 +![image](hmosword-build/image/1.png) +3. 连接设备,点击签名文件入口进行签名,按照图示进行勾选,最后点击ok完成签名。 +![image](hmosword-build/image/2.png) +4. 点击运行,等待编译完成。 +![image](hmosword-build/image/3.PNG) + +#### 华为智能穿戴设备运行 +1. DevEco studio打开工程文件,等待Sync完成。 +2. 点击运行入口配置,选中wearable。 +![image](hmosword-build/image/4.PNG) +3. 连接设备,点击签名文件入口进行签名,最后点击ok完成签名。 +4. 点击运行,等待编译完成。 +![image](hmosword-build/image/3.PNG) + +### 2、集成Sample后,运行HMOS世界 + +#### Sample下载 +1. 确保电脑中已成功安装git,打开DevEco studio终端(Terminal)。 +2. 执行`cd hmosword-build`进入到[hmosword-build](hmosword-build)目录。 +![image](hmosword-build/image/5.png) +3. 再执行`npm i`下载依赖包。如果出现以下错误,请在终端执行`npm config set registry https://registry.npmjs.org/` 设置官方镜像源。 +![image](hmosword-build/image/6.png) + +4. 依赖包下載成功后,执行 `node .\index.js`运行脚本。此步骤会全量下载工程依赖的sample,并且更新HMOS世界的[build-profile.json5](build-profile.json5)配置文件。下載过程中会由于网络情况出现部分sample下载失败,可以在当前任务结束后重复执行`node .\index.js`。 +5. 确保sample成功下载后,点击DevEco studio的File->Sync and refresh Project重新编译。 + +#### 手机、折叠屏、平板、PC/2in1设备集成sample运行 +以下sample支持手机、折叠屏、平板、PC/2in1: +animationcollectionsample、audiointeractionsample、componentstacksample、customdialogsample、dragframeworksample、fluentblogsample、gridhybridsample、imagecommentsample、keyboardsample、listexchangesample、listitemeditsample、locationservicesample、multibusinesssample、multicolumnssample、multiconvinientlifesample、multimobilepaymentsample 、multinavbarsample、multinewsreadsample、multipleimagesample、multitabnavigationsample、multitravelsample、nestedslidingsample、pageredirectionsample、pickersample、preferencessample、texteffectssample、transitionscollectionsample、verificationcodescenariosample、waterflowsample、webprerendersample、windowpipsample、continuepublishsample、liveviewlockscreensample、videocastsample、knocksharesample。 +1. 修改运行入口,选中phone点击`Deploy Multi Hap`勾选支持手机、折叠屏、平板、PC/2in1的hap,注意不要勾选其余hap包(smartwatchshortvideosample、smartwatchmapsample、smartwatchcarcontrolsample、wearable、wearablemusicsample)。 +![image](hmosword-build/image/7.PNG) +2. 成功勾选后点击OK完成配置修改。 +3. 点击运行,等待编译完成。 + +#### 华为智能穿戴设备集成sample运行 +以下sample支持华为智能穿戴设备:smartwatchshortvideosample、smartwatchmapsample、smartwatchcarcontrolsample、wearablemusicsample。 +1. 修改运行入口,选中wearable后,点击`Deploy Multi Hap`勾选支持华为智能穿戴设备的hap,注意不要勾选其余hap包。 +![image](hmosword-build/image/8.PNG) +2. 成功勾选后点击OK完成配置修改。 +3. 点击运行,等待编译完成。 + +## 六、约束与限制 +1. 本示例仅支持标准系统上运行,支持多种设备:华为智能穿戴设备、手机、折叠屏、平板、PC/2in1等。 +2. HarmonyOS系统:HarmonyOS 5.1.0 Release及以上。 +3. DevEco Studio版本:DevEco Studio 5.1.0 Release及以上。 +4. HarmonyOS SDK版本:HarmonyOS 5.1.0 Release SDK及以上。 + +## 七、版本更新说明 +- HMOS世界V1.0.0.1版本: 初始化组件、样例、实践和我的模块。 \ No newline at end of file diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d5f77eeb5eb8daf4af86ee4c90a4de9e777e4434 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,71 @@ +{ + "app": { + "signingConfigs": [], + "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..e817b3ce73edebe54fee271d09b06bd398352a40 --- /dev/null +++ b/common/src/main/ets/util/DynamicInstallManager.ets @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 { BusinessError, emitter } from '@kit.BasicServicesKit'; +import { moduleInstallManager } from '@kit.StoreKit'; +import { display } from '@kit.ArkUI'; +import { CommonConstants } from '../../../../Index'; +import Logger from './Logger'; + +const DOWNLOAD_TIMEOUT_LIMIT: number = 1800; +const TAG: string = '[DynamicInstallManager]'; + +export class DynamicInstallManager { + 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 option: StartOptions = DynamicInstallManager.setStartAbilityProperty(); + context.startAbility({ bundleName: context.abilityInfo.bundleName, abilityName: moduleAbility }, option) + .then(() => { + 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.width * CommonConstants.WINDOW_RATIO; + const windowHeight = displayData.availableHeight * CommonConstants.WINDOW_RATIO; + const windowLeft = (windowWidth - CommonConstants.WINDOW_RATIO * windowWidth) / 2.0; + const windowTop = (windowHeight - CommonConstants.WINDOW_RATIO * windowHeight) / 2.0; + const option: StartOptions = { + minWindowWidth: CommonConstants.MIN_WINDOW_WIDTH, + minWindowHeight: 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..a7f89b46103ad911cd11376c0fc389538e963549 --- /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": "HMOS世界 - 示例代码合集", + "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": "探索HMOS世界一多开发实践", + "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..4353d89f44e9e00f0eae8118947195ca642d9a8b --- /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": "HMOS世界", + "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..76192b9dd6450c97bcf8ed0dbf1f32ae15b45625 --- /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": "HMOS世界 示例代码合集", + "desc": "HMOS世界App是华为官方出品的一款大型开源开发示范应用。", + "preInstalled": true, + "sampleType": "commonClient", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_all_device.png", + "originalUrl": "", + "moduleName": "", + "abilityName": "", + "order": 1 + }, + { + "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": 63, + "title": "HMOS世界(手表版)", + "desc": "HMOS世界手表开发案例。", + "preInstalled": true, + "sampleType": "wearableClient", + "isFavorite": false, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_watch.png", + "originalUrl": "", + "moduleName": "", + "abilityName": "", + "order": 7 + } + ] + }, + { + "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..87e3586de49742cf90652a95c032afc349d813c4 --- /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": "HMOS世界 示例代码合集", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_green.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 60, + "type": 2, + "cardId": 25, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_all_device.png", + "title": "HMOS世界 示例代码合集", + "subTitle": "", + "tags": [ + "该案例已支持多设备" + ], + "order": 1 + } + ] + }, + { + "id": 61, + "cardTitle": "三折叠,怎么折都有面", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_blue.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_pink.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_blue.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_pink.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_green.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": 63, + "cardTitle": "HMOS世界(手表版)", + "cardStyleType": 5, + "cardImage": "image/common/sample_card_background_pink.png", + "version": 1000000, + "detailCardId": 35, + "sampleContents": [ + { + "id": 63, + "type": 2, + "cardId": 28, + "mediaType": 1, + "mediaUrl": "image/sample/hdc/hmosworld_watch.png", + "title": "HMOS世界(手表版)", + "subTitle": "", + "tags": [ + "使用Watch5体验完整效果" + ], + "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/resfile/codePreview/css/JetBrainsMonoNL-Regular.ttf b/features/componentlibrary/src/main/resources/resfile/codePreview/css/JetBrainsMonoNL-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..70d2ec9e28d6e06e923629b78b8a12c12e841589 Binary files /dev/null and b/features/componentlibrary/src/main/resources/resfile/codePreview/css/JetBrainsMonoNL-Regular.ttf differ diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/css/index.css b/features/componentlibrary/src/main/resources/resfile/codePreview/css/index.css new file mode 100644 index 0000000000000000000000000000000000000000..b09ace957fe6cd62187ba8233ae296dd348c0a1d --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/css/index.css @@ -0,0 +1,45 @@ +@font-face { + font-family: "JetBrains"; + src: url(./JetBrainsMonoNL-Regular.ttf); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + height: 100%; + width: 100%; +} + +body { + height: 100%; + width: 100%; + font-size: 14px; + position: relative; +} + +pre { + margin: 0; + width: 100%; + min-height: 100%; + overflow-y: hidden; + overflow-x: scroll; + padding-top: 12px; +} + +#code { + overflow-y: hidden; + overflow-x: hidden; + width: 100%; +} + +code { + font-family: "JetBrains"; +} + +pre code.hljs { + padding: 0; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/dist/dark.css b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/dark.css new file mode 100644 index 0000000000000000000000000000000000000000..bd5dcc5eb4298576ea0bd672c671982eee334c8d --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/dark.css @@ -0,0 +1,7 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-variable,.hljs-template-variable,.hljs-tag,.hljs-name,.hljs-selector-id,.hljs-selector-class,.hljs-regexp,.hljs-deletion{color:#ffa07a}.hljs-number,.hljs-built_in,.hljs-literal,.hljs-type,.hljs-params,.hljs-meta,.hljs-link{color:#f5ab35}.hljs-attribute{color:gold}.hljs-string,.hljs-symbol,.hljs-bullet,.hljs-addition{color:#abe338}.hljs-title,.hljs-section{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast: active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-string,.hljs-symbol,.hljs-type,.hljs-quote{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}.code{background:transparent}.hljs{background:transparent;color:#f8f8f2} diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/dist/dark.js b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/dark.js new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/dark.js @@ -0,0 +1 @@ + diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/dist/hl.js b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/hl.js new file mode 100644 index 0000000000000000000000000000000000000000..f02ba035d3dcbb0c921c85b6c7dc4efae69b773d --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/hl.js @@ -0,0 +1 @@ +import{H as i}from"../../dist/common.js";import"../../dist/common2.js";window.hljs=i; diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/dist/light.css b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/light.css new file mode 100644 index 0000000000000000000000000000000000000000..f16721b141a7e5472a6e05ee6255e3e4e6737493 --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/light.css @@ -0,0 +1,7 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! + Theme: a11y-light + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/.hljs{background:#fefefe;color:#545454}.hljs-comment,.hljs-quote{color:#696969}.hljs-variable,.hljs-template-variable,.hljs-tag,.hljs-name,.hljs-selector-id,.hljs-selector-class,.hljs-regexp,.hljs-deletion{color:#d91e18}.hljs-number,.hljs-built_in,.hljs-literal,.hljs-type,.hljs-params,.hljs-meta,.hljs-link,.hljs-attribute{color:#aa5d00}.hljs-string,.hljs-symbol,.hljs-bullet,.hljs-addition{color:green}.hljs-title,.hljs-section{color:#007faa}.hljs-keyword,.hljs-selector-tag{color:#7928a1}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast: active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-string,.hljs-symbol,.hljs-type,.hljs-quote{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}.code{background:transparent}.hljs{background:transparent;color:#545454} diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/dist/light.js b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/light.js new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/dist/light.js @@ -0,0 +1 @@ + diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/index.html b/features/componentlibrary/src/main/resources/resfile/codePreview/index.html new file mode 100644 index 0000000000000000000000000000000000000000..096de95dfbd4ae481190c6966bca4b32f04f4cd8 --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/index.html @@ -0,0 +1,17 @@ + + + + + CodePreview + + + + + +

+
+
+ + + + \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/resfile/codePreview/js/index.js b/features/componentlibrary/src/main/resources/resfile/codePreview/js/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f4b62e658a691a142088e75ce47b00dfdee07e99 --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/codePreview/js/index.js @@ -0,0 +1,77 @@ +const code = document.getElementById('code'); +const pre = document.getElementsByTagName('pre')[0]; +const link = document.getElementsByTagName('link')[0]; +const codeContainer = document.getElementsByTagName('code')[0]; +const LIGHT = 1; +const DARK = 0; +const UP = 'up'; +const DOWN = 'down'; + +let lastScrollTop = 0; +code.addEventListener('scroll', handleScroll); + +function toFullScreen() { + code.style.overflowY = 'scroll'; + code.style.paddingLeft = '16px'; + code.style.paddingRight = '16px'; + pre.style.paddingTop = '100px'; + pre.style.paddingBottom = '70px'; +} + +function toSmallScreen() { + code.scrollTo({top: 0}); + code.style.overflowY = 'hidden'; + pre.style.paddingTop = '12px'; + pre.style.paddingBottom = '0px'; + code.style.paddingLeft = '0px'; + code.style.paddingRight = '0px'; +} + +function changeColorMode(colorMode) { + link.href = colorMode === LIGHT ? './dist/light.css' : './dist/dark.css'; +} + +function codeToHtml(codeParam, colorMode) { + codeContainer.innerHTML = codeParam; + delete codeContainer.dataset.highlighted; + if (colorMode !== undefined) { + changeColorMode(colorMode); + } + window.hljs.highlightAll(); +} + +function showLandscapeView(breakPoint) { + code.scrollTo({top: 0}); + pre.style.paddingTop = breakPoint === 'sm' ? '60px' : '100px'; + code.style.paddingLeft = breakPoint === 'sm' ? '56px' : '12px'; +} + +function showLandscapeFloatView(breakPoint) { + code.scrollTo({top: 0}); + pre.style.paddingTop = breakPoint === 'sm' ? '60px' : '100px'; + code.style.paddingLeft = breakPoint === 'sm' ? '16px' : '12px'; +} + +function showPortraitView() { + code.scrollTo({top: 0}); + code.style.paddingLeft = '16px'; + pre.style.paddingTop = '100px'; +} + +function handleScroll() { + const scrollTop = code.scrollTop; + if (scrollTop > lastScrollTop) { + scrollManager.updateScrollDirection(DOWN); + } else if (scrollTop < lastScrollTop) { + scrollManager.updateScrollDirection(UP); + } + lastScrollTop = scrollTop; +} + +function changeHeightStyle() { + document.getElementById('code').style.height = '100%'; +} + +function removeHeightStyle() { + document.getElementById('code').style.height = 'auto'; +} \ No newline at end of file diff --git a/features/componentlibrary/src/main/resources/resfile/dist/common.js b/features/componentlibrary/src/main/resources/resfile/dist/common.js new file mode 100644 index 0000000000000000000000000000000000000000..029161456131809c3173464779ddf5ffd22ee439 --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/dist/common.js @@ -0,0 +1,4 @@ +import{g as St}from"./common2.js";function ct(n){return n instanceof Map?n.clear=n.delete=n.set=function(){throw new Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=function(){throw new Error("set is read-only")}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach(e=>{const t=n[e],s=typeof t;(s==="object"||s==="function")&&!Object.isFrozen(t)&&ct(t)}),n}class Nn{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function lt(n){return n.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function ue(n,...e){const t=Object.create(null);for(const s in n)t[s]=n[s];return e.forEach(function(s){for(const d in s)t[d]=s[d]}),t}const vt="",hn=n=>!!n.scope,Ot=(n,{prefix:e})=>{if(n.startsWith("language:"))return n.replace("language:","language-");if(n.includes(".")){const t=n.split(".");return[`${e}${t.shift()}`,...t.map((s,d)=>`${s}${"_".repeat(d+1)}`)].join(" ")}return`${e}${n}`};class wt{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=lt(e)}openNode(e){if(!hn(e))return;const t=Ot(e.scope,{prefix:this.classPrefix});this.span(t)}closeNode(e){hn(e)&&(this.buffer+=vt)}value(){return this.buffer}span(e){this.buffer+=``}}const yn=(n={})=>{const e={children:[]};return Object.assign(e,n),e};class _n{constructor(){this.rootNode=yn(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t=yn({scope:e});this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return typeof t=="string"?e.addText(t):t.children&&(e.openNode(t),t.children.forEach(s=>this._walk(e,s)),e.closeNode(t)),e}static _collapse(e){typeof e!="string"&&e.children&&(e.children.every(t=>typeof t=="string")?e.children=[e.children.join("")]:e.children.forEach(t=>{_n._collapse(t)}))}}class Rt extends _n{constructor(e){super(),this.options=e}addText(e){e!==""&&this.add(e)}startScope(e){this.openNode(e)}endScope(){this.closeNode()}__addSublanguage(e,t){const s=e.root;t&&(s.scope=`language:${t}`),this.add(s)}toHTML(){return new wt(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}}function he(n){return n?typeof n=="string"?n:n.source:null}function dt(n){return pe("(?=",n,")")}function At(n){return pe("(?:",n,")*")}function Mt(n){return pe("(?:",n,")?")}function pe(...n){return n.map(t=>he(t)).join("")}function Ct(n){const e=n[n.length-1];return typeof e=="object"&&e.constructor===Object?(n.splice(n.length-1,1),e):{}}function En(...n){return"("+(Ct(n).capture?"":"?:")+n.map(s=>he(s)).join("|")+")"}function ut(n){return new RegExp(n.toString()+"|").exec("").length-1}function It(n,e){const t=n&&n.exec(e);return t&&t.index===0}const kt=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function mn(n,{joinWith:e}){let t=0;return n.map(s=>{t+=1;const d=t;let o=he(s),i="";for(;o.length>0;){const r=kt.exec(o);if(!r){i+=o;break}i+=o.substring(0,r.index),o=o.substring(r.index+r[0].length),r[0][0]==="\\"&&r[1]?i+="\\"+String(Number(r[1])+d):(i+=r[0],r[0]==="("&&t++)}return i}).map(s=>`(${s})`).join(e)}const xt=/\b\B/,gt="[a-zA-Z]\\w*",fn="[a-zA-Z_]\\w*",bt="\\b\\d+(\\.\\d+)?",pt="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_t="\\b(0b[01]+)",Dt="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",Lt=(n={})=>{const e=/^#![ ]*\//;return n.binary&&(n.begin=pe(e,/.*\b/,n.binary,/\b.*/)),ue({scope:"meta",begin:e,end:/$/,relevance:0,"on:begin":(t,s)=>{t.index!==0&&s.ignoreMatch()}},n)},ye={begin:"\\\\[\\s\\S]",relevance:0},Bt={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[ye]},Ut={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[ye]},Pt={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},Ae=function(n,e,t={}){const s=ue({scope:"comment",begin:n,end:e,contains:[]},t);s.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});const d=En("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return s.contains.push({begin:pe(/[ ]+/,"(",d,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s},Ft=Ae("//","$"),$t=Ae("/\\*","\\*/"),zt=Ae("#","$"),Kt={scope:"number",begin:bt,relevance:0},qt={scope:"number",begin:pt,relevance:0},Gt={scope:"number",begin:_t,relevance:0},Ht={scope:"regexp",begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[ye,{begin:/\[/,end:/\]/,relevance:0,contains:[ye]}]},Wt={scope:"title",begin:gt,relevance:0},Yt={scope:"title",begin:fn,relevance:0},Zt={begin:"\\.\\s*"+fn,relevance:0},Xt=function(n){return Object.assign(n,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})};var we=Object.freeze({__proto__:null,APOS_STRING_MODE:Bt,BACKSLASH_ESCAPE:ye,BINARY_NUMBER_MODE:Gt,BINARY_NUMBER_RE:_t,COMMENT:Ae,C_BLOCK_COMMENT_MODE:$t,C_LINE_COMMENT_MODE:Ft,C_NUMBER_MODE:qt,C_NUMBER_RE:pt,END_SAME_AS_BEGIN:Xt,HASH_COMMENT_MODE:zt,IDENT_RE:gt,MATCH_NOTHING_RE:xt,METHOD_GUARD:Zt,NUMBER_MODE:Kt,NUMBER_RE:bt,PHRASAL_WORDS_MODE:Pt,QUOTE_STRING_MODE:Ut,REGEXP_MODE:Ht,RE_STARTERS_RE:Dt,SHEBANG:Lt,TITLE_MODE:Wt,UNDERSCORE_IDENT_RE:fn,UNDERSCORE_TITLE_MODE:Yt});function Vt(n,e){n.input[n.index-1]==="."&&e.ignoreMatch()}function Qt(n,e){n.className!==void 0&&(n.scope=n.className,delete n.className)}function Jt(n,e){e&&n.beginKeywords&&(n.begin="\\b("+n.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",n.__beforeBegin=Vt,n.keywords=n.keywords||n.beginKeywords,delete n.beginKeywords,n.relevance===void 0&&(n.relevance=0))}function jt(n,e){Array.isArray(n.illegal)&&(n.illegal=En(...n.illegal))}function er(n,e){if(n.match){if(n.begin||n.end)throw new Error("begin & end are not supported with match");n.begin=n.match,delete n.match}}function nr(n,e){n.relevance===void 0&&(n.relevance=1)}const tr=(n,e)=>{if(!n.beforeMatch)return;if(n.starts)throw new Error("beforeMatch cannot be used with starts");const t=Object.assign({},n);Object.keys(n).forEach(s=>{delete n[s]}),n.keywords=t.keywords,n.begin=pe(t.beforeMatch,dt(t.begin)),n.starts={relevance:0,contains:[Object.assign(t,{endsParent:!0})]},n.relevance=0,delete t.beforeMatch},rr=["of","and","for","in","not","or","if","then","parent","list","value"],ar="keyword";function Et(n,e,t=ar){const s=Object.create(null);return typeof n=="string"?d(t,n.split(" ")):Array.isArray(n)?d(t,n):Object.keys(n).forEach(function(o){Object.assign(s,Et(n[o],e,o))}),s;function d(o,i){e&&(i=i.map(r=>r.toLowerCase())),i.forEach(function(r){const a=r.split("|");s[a[0]]=[o,ir(a[0],a[1])]})}}function ir(n,e){return e?Number(e):sr(n)?0:1}function sr(n){return rr.includes(n.toLowerCase())}const Tn={},be=n=>{console.error(n)},Sn=(n,...e)=>{console.log(`WARN: ${n}`,...e)},me=(n,e)=>{Tn[`${n}/${e}`]||(console.log(`Deprecated as of ${n}. ${e}`),Tn[`${n}/${e}`]=!0)},Re=new Error;function mt(n,e,{key:t}){let s=0;const d=n[t],o={},i={};for(let r=1;r<=e.length;r++)i[r+s]=d[r],o[r+s]=!0,s+=ut(e[r-1]);n[t]=i,n[t]._emit=o,n[t]._multi=!0}function or(n){if(Array.isArray(n.begin)){if(n.skip||n.excludeBegin||n.returnBegin)throw be("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),Re;if(typeof n.beginScope!="object"||n.beginScope===null)throw be("beginScope must be object"),Re;mt(n,n.begin,{key:"beginScope"}),n.begin=mn(n.begin,{joinWith:""})}}function cr(n){if(Array.isArray(n.end)){if(n.skip||n.excludeEnd||n.returnEnd)throw be("skip, excludeEnd, returnEnd not compatible with endScope: {}"),Re;if(typeof n.endScope!="object"||n.endScope===null)throw be("endScope must be object"),Re;mt(n,n.end,{key:"endScope"}),n.end=mn(n.end,{joinWith:""})}}function lr(n){n.scope&&typeof n.scope=="object"&&n.scope!==null&&(n.beginScope=n.scope,delete n.scope)}function dr(n){lr(n),typeof n.beginScope=="string"&&(n.beginScope={_wrap:n.beginScope}),typeof n.endScope=="string"&&(n.endScope={_wrap:n.endScope}),or(n),cr(n)}function ur(n){function e(i,r){return new RegExp(he(i),"m"+(n.case_insensitive?"i":"")+(n.unicodeRegex?"u":"")+(r?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(r,a){a.position=this.position++,this.matchIndexes[this.matchAt]=a,this.regexes.push([a,r]),this.matchAt+=ut(r)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);const r=this.regexes.map(a=>a[1]);this.matcherRe=e(mn(r,{joinWith:"|"}),!0),this.lastIndex=0}exec(r){this.matcherRe.lastIndex=this.lastIndex;const a=this.matcherRe.exec(r);if(!a)return null;const l=a.findIndex((g,b)=>b>0&&g!==void 0),c=this.matchIndexes[l];return a.splice(0,l),Object.assign(a,c)}}class s{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(r){if(this.multiRegexes[r])return this.multiRegexes[r];const a=new t;return this.rules.slice(r).forEach(([l,c])=>a.addRule(l,c)),a.compile(),this.multiRegexes[r]=a,a}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(r,a){this.rules.push([r,a]),a.type==="begin"&&this.count++}exec(r){const a=this.getMatcher(this.regexIndex);a.lastIndex=this.lastIndex;let l=a.exec(r);if(this.resumingScanAtSamePosition()&&!(l&&l.index===this.lastIndex)){const c=this.getMatcher(0);c.lastIndex=this.lastIndex+1,l=c.exec(r)}return l&&(this.regexIndex+=l.position+1,this.regexIndex===this.count&&this.considerAll()),l}}function d(i){const r=new s;return i.contains.forEach(a=>r.addRule(a.begin,{rule:a,type:"begin"})),i.terminatorEnd&&r.addRule(i.terminatorEnd,{type:"end"}),i.illegal&&r.addRule(i.illegal,{type:"illegal"}),r}function o(i,r){const a=i;if(i.isCompiled)return a;[Qt,er,dr,tr].forEach(c=>c(i,r)),n.compilerExtensions.forEach(c=>c(i,r)),i.__beforeBegin=null,[Jt,jt,nr].forEach(c=>c(i,r)),i.isCompiled=!0;let l=null;return typeof i.keywords=="object"&&i.keywords.$pattern&&(i.keywords=Object.assign({},i.keywords),l=i.keywords.$pattern,delete i.keywords.$pattern),l=l||/\w+/,i.keywords&&(i.keywords=Et(i.keywords,n.case_insensitive)),a.keywordPatternRe=e(l,!0),r&&(i.begin||(i.begin=/\B|\b/),a.beginRe=e(a.begin),!i.end&&!i.endsWithParent&&(i.end=/\B|\b/),i.end&&(a.endRe=e(a.end)),a.terminatorEnd=he(a.end)||"",i.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(i.end?"|":"")+r.terminatorEnd)),i.illegal&&(a.illegalRe=e(i.illegal)),i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map(function(c){return gr(c==="self"?i:c)})),i.contains.forEach(function(c){o(c,a)}),i.starts&&o(i.starts,r),a.matcher=d(a),a}if(n.compilerExtensions||(n.compilerExtensions=[]),n.contains&&n.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return n.classNameAliases=ue(n.classNameAliases||{}),o(n)}function ft(n){return n?n.endsWithParent||ft(n.starts):!1}function gr(n){return n.variants&&!n.cachedVariants&&(n.cachedVariants=n.variants.map(function(e){return ue(n,{variants:null},e)})),n.cachedVariants?n.cachedVariants:ft(n)?ue(n,{starts:n.starts?ue(n.starts):null}):Object.isFrozen(n)?ue(n):n}var br="11.9.0";class pr extends Error{constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}}const Ie=lt,vn=ue,On=Symbol("nomatch"),_r=7,Nt=function(n){const e=Object.create(null),t=Object.create(null),s=[];let d=!0;const o="Could not find the language '{}', did you forget to load/include a language module?",i={disableAutodetect:!0,name:"Plain text",contains:[]};let r={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:Rt};function a(u){return r.noHighlightRe.test(u)}function l(u){let f=u.className+" ";f+=u.parentNode?u.parentNode.className:"";const w=r.languageDetectRe.exec(f);if(w){const k=I(w[1]);return k||(Sn(o.replace("{}",w[1])),Sn("Falling back to no-highlight mode for this block.",u)),k?w[1]:"no-highlight"}return f.split(/\s+/).find(k=>a(k)||I(k))}function c(u,f,w){let k="",U="";typeof f=="object"?(k=u,w=f.ignoreIllegals,U=f.language):(me("10.7.0","highlight(lang, code, ...args) has been deprecated."),me("10.7.0",`Please use highlight(code, options) instead. +https://github.com/highlightjs/highlight.js/issues/2277`),U=u,k=f),w===void 0&&(w=!0);const Y={code:k,language:U};B("before:highlight",Y);const ne=Y.result?Y.result:g(Y.language,Y.code,w);return ne.code=Y.code,B("after:highlight",ne),ne}function g(u,f,w,k){const U=Object.create(null);function Y(_,y){return _.keywords[y]}function ne(){if(!S.keywords){G.addText(P);return}let _=0;S.keywordPatternRe.lastIndex=0;let y=S.keywordPatternRe.exec(P),v="";for(;y;){v+=P.substring(_,y.index);const x=re.case_insensitive?y[0].toLowerCase():y[0],X=Y(S,x);if(X){const[se,Me]=X;if(G.addText(v),v="",U[x]=(U[x]||0)+1,U[x]<=_r&&(Ee+=Me),se.startsWith("_"))v+=y[0];else{const Ce=re.classNameAliases[se]||se;Z(y[0],Ce)}}else v+=y[0];_=S.keywordPatternRe.lastIndex,y=S.keywordPatternRe.exec(P)}v+=P.substring(_),G.addText(v)}function te(){if(P==="")return;let _=null;if(typeof S.subLanguage=="string"){if(!e[S.subLanguage]){G.addText(P);return}_=g(S.subLanguage,P,!0,ve[S.subLanguage]),ve[S.subLanguage]=_._top}else _=p(P,S.subLanguage.length?S.subLanguage:null);S.relevance>0&&(Ee+=_.relevance),G.__addSublanguage(_._emitter,_.language)}function z(){S.subLanguage!=null?te():ne(),P=""}function Z(_,y){_!==""&&(G.startScope(y),G.addText(_),G.endScope())}function q(_,y){let v=1;const x=y.length-1;for(;v<=x;){if(!_._emit[v]){v++;continue}const X=re.classNameAliases[_[v]]||_[v],se=y[v];X?Z(se,X):(P=se,ne(),P=""),v++}}function K(_,y){return _.scope&&typeof _.scope=="string"&&G.openNode(re.classNameAliases[_.scope]||_.scope),_.beginScope&&(_.beginScope._wrap?(Z(P,re.classNameAliases[_.beginScope._wrap]||_.beginScope._wrap),P=""):_.beginScope._multi&&(q(_.beginScope,y),P="")),S=Object.create(_,{parent:{value:S}}),S}function Q(_,y,v){let x=It(_.endRe,v);if(x){if(_["on:end"]){const X=new Nn(_);_["on:end"](y,X),X.isMatchIgnored&&(x=!1)}if(x){for(;_.endsParent&&_.parent;)_=_.parent;return _}}if(_.endsWithParent)return Q(_.parent,y,v)}function j(_){return S.matcher.regexIndex===0?(P+=_[0],1):(ce=!0,0)}function ee(_){const y=_[0],v=_.rule,x=new Nn(v),X=[v.__beforeBegin,v["on:begin"]];for(const se of X)if(se&&(se(_,x),x.isMatchIgnored))return j(y);return v.skip?P+=y:(v.excludeBegin&&(P+=y),z(),!v.returnBegin&&!v.excludeBegin&&(P=y)),K(v,_),v.returnBegin?0:y.length}function ie(_){const y=_[0],v=f.substring(_.index),x=Q(S,_,v);if(!x)return On;const X=S;S.endScope&&S.endScope._wrap?(z(),Z(y,S.endScope._wrap)):S.endScope&&S.endScope._multi?(z(),q(S.endScope,_)):X.skip?P+=y:(X.returnEnd||X.excludeEnd||(P+=y),z(),X.excludeEnd&&(P=y));do S.scope&&G.closeNode(),!S.skip&&!S.subLanguage&&(Ee+=S.relevance),S=S.parent;while(S!==x.parent);return x.starts&&K(x.starts,_),X.returnEnd?0:y.length}function oe(){const _=[];for(let y=S;y!==re;y=y.parent)y.scope&&_.unshift(y.scope);_.forEach(y=>G.openNode(y))}let ge={};function Te(_,y){const v=y&&y[0];if(P+=_,v==null)return z(),0;if(ge.type==="begin"&&y.type==="end"&&ge.index===y.index&&v===""){if(P+=f.slice(y.index,y.index+1),!d){const x=new Error(`0 width match regex (${u})`);throw x.languageName=u,x.badRule=ge.rule,x}return 1}if(ge=y,y.type==="begin")return ee(y);if(y.type==="illegal"&&!w){const x=new Error('Illegal lexeme "'+v+'" for mode "'+(S.scope||"")+'"');throw x.mode=S,x}else if(y.type==="end"){const x=ie(y);if(x!==On)return x}if(y.type==="illegal"&&v==="")return 1;if(de>1e5&&de>y.index*3)throw new Error("potential infinite loop, way more iterations than matches");return P+=v,v.length}const re=I(u);if(!re)throw be(o.replace("{}",u)),new Error('Unknown language: "'+u+'"');const Se=ur(re);let _e="",S=k||Se;const ve={},G=new r.__emitter(r);oe();let P="",Ee=0,le=0,de=0,ce=!1;try{if(re.__emitTokens)re.__emitTokens(f,G);else{for(S.matcher.considerAll();;){de++,ce?ce=!1:S.matcher.considerAll(),S.matcher.lastIndex=le;const _=S.matcher.exec(f);if(!_)break;const y=f.substring(le,_.index),v=Te(y,_);le=_.index+v}Te(f.substring(le))}return G.finalize(),_e=G.toHTML(),{language:u,value:_e,relevance:Ee,illegal:!1,_emitter:G,_top:S}}catch(_){if(_.message&&_.message.includes("Illegal"))return{language:u,value:Ie(f),illegal:!0,relevance:0,_illegalBy:{message:_.message,index:le,context:f.slice(le-100,le+100),mode:_.mode,resultSoFar:_e},_emitter:G};if(d)return{language:u,value:Ie(f),illegal:!1,relevance:0,errorRaised:_,_emitter:G,_top:S};throw _}}function b(u){const f={value:Ie(u),illegal:!1,relevance:0,_top:i,_emitter:new r.__emitter(r)};return f._emitter.addText(u),f}function p(u,f){f=f||r.languages||Object.keys(e);const w=b(u),k=f.filter(I).filter(F).map(z=>g(z,u,!1));k.unshift(w);const U=k.sort((z,Z)=>{if(z.relevance!==Z.relevance)return Z.relevance-z.relevance;if(z.language&&Z.language){if(I(z.language).supersetOf===Z.language)return 1;if(I(Z.language).supersetOf===z.language)return-1}return 0}),[Y,ne]=U,te=Y;return te.secondBest=ne,te}function E(u,f,w){const k=f&&t[f]||w;u.classList.add("hljs"),u.classList.add(`language-${k}`)}function m(u){let f=null;const w=l(u);if(a(w))return;if(B("before:highlightElement",{el:u,language:w}),u.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",u);return}if(u.children.length>0&&(r.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(u)),r.throwUnescapedHTML))throw new pr("One of your code blocks includes unescaped HTML.",u.innerHTML);f=u;const k=f.textContent,U=w?c(k,{language:w,ignoreIllegals:!0}):p(k);u.innerHTML=U.value,u.dataset.highlighted="yes",E(u,w,U.language),u.result={language:U.language,re:U.relevance,relevance:U.relevance},U.secondBest&&(u.secondBest={language:U.secondBest.language,relevance:U.secondBest.relevance}),B("after:highlightElement",{el:u,result:U,text:k})}function h(u){r=vn(r,u)}const N=()=>{M(),me("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function O(){M(),me("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let C=!1;function M(){if(document.readyState==="loading"){C=!0;return}document.querySelectorAll(r.cssSelector).forEach(m)}function A(){C&&M()}typeof window<"u"&&window.addEventListener&&window.addEventListener("DOMContentLoaded",A,!1);function D(u,f){let w=null;try{w=f(n)}catch(k){if(be("Language definition for '{}' could not be registered.".replace("{}",u)),d)be(k);else throw k;w=i}w.name||(w.name=u),e[u]=w,w.rawDefinition=f.bind(null,n),w.aliases&&H(w.aliases,{languageName:u})}function L(u){delete e[u];for(const f of Object.keys(t))t[f]===u&&delete t[f]}function T(){return Object.keys(e)}function I(u){return u=(u||"").toLowerCase(),e[u]||e[t[u]]}function H(u,{languageName:f}){typeof u=="string"&&(u=[u]),u.forEach(w=>{t[w.toLowerCase()]=f})}function F(u){const f=I(u);return f&&!f.disableAutodetect}function J(u){u["before:highlightBlock"]&&!u["before:highlightElement"]&&(u["before:highlightElement"]=f=>{u["before:highlightBlock"](Object.assign({block:f.el},f))}),u["after:highlightBlock"]&&!u["after:highlightElement"]&&(u["after:highlightElement"]=f=>{u["after:highlightBlock"](Object.assign({block:f.el},f))})}function ae(u){J(u),s.push(u)}function W(u){const f=s.indexOf(u);f!==-1&&s.splice(f,1)}function B(u,f){const w=u;s.forEach(function(k){k[w]&&k[w](f)})}function V(u){return me("10.7.0","highlightBlock will be removed entirely in v12.0"),me("10.7.0","Please use highlightElement now."),m(u)}Object.assign(n,{highlight:c,highlightAuto:p,highlightAll:M,highlightElement:m,highlightBlock:V,configure:h,initHighlighting:N,initHighlightingOnLoad:O,registerLanguage:D,unregisterLanguage:L,listLanguages:T,getLanguage:I,registerAliases:H,autoDetection:F,inherit:vn,addPlugin:ae,removePlugin:W}),n.debugMode=function(){d=!1},n.safeMode=function(){d=!0},n.versionString=br,n.regex={concat:pe,lookahead:dt,either:En,optional:Mt,anyNumberOfTimes:At};for(const u in we)typeof we[u]=="object"&&ct(we[u]);return Object.assign(n,we),n},fe=Nt({});fe.newInstance=()=>Nt({});var Er=fe;fe.HighlightJS=fe;fe.default=fe;var ke,wn;function mr(){if(wn)return ke;wn=1;function n(e){const t=e.regex,s=t.concat(/[\p{L}_]/u,t.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),d=/[\p{L}0-9._:-]+/u,o={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/,contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},r=e.inherit(i,{begin:/\(/,end:/\)/}),a=e.inherit(e.APOS_STRING_MODE,{className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,l,a,r,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[i,r,l,a]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},o,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[c],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[c],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:t.concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:s,relevance:0,starts:c}]},{className:"tag",begin:t.concat(/<\//,t.lookahead(t.concat(s,/>/))),contains:[{className:"name",begin:s,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}return ke=n,ke}var xe,Rn;function fr(){if(Rn)return xe;Rn=1;function n(e){const t=e.regex,s={},d={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[s]}]};Object.assign(s,{className:"variable",variants:[{begin:t.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},d]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},r={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,o]};o.contains.push(r);const a={match:/\\"/},l={className:"string",begin:/'/,end:/'/},c={match:/\\'/},g={begin:/\$?\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},b=["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"],p=e.SHEBANG({binary:`(${b.join("|")})`,relevance:10}),E={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},m=["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"],h=["true","false"],N={match:/(\/[a-z._-]+)+/},O=["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset"],C=["alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias"],M=["autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp"],A=["chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"];return{name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/,keyword:m,literal:h,built_in:[...O,...C,"set","shopt",...M,...A]},contains:[p,e.SHEBANG(),E,g,e.HASH_COMMENT_MODE,i,N,r,a,l,c,s]}}return xe=n,xe}var De,An;function Nr(){if(An)return De;An=1;function n(e){const t=e.regex,s=e.COMMENT("//","$",{contains:[{begin:/\\\n/}]}),d="decltype\\(auto\\)",o="[a-zA-Z_]\\w*::",r="("+d+"|"+t.optional(o)+"[a-zA-Z_]\\w*"+t.optional("<[^<>]+>")+")",a={className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{match:/\batomic_[a-z]{3,6}\b/}]},c={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'("+"\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)"+"|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},g={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},b={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(c,{className:"string"}),{className:"string",begin:/<.*?>/},s,e.C_BLOCK_COMMENT_MODE]},p={className:"title",begin:t.optional(o)+e.IDENT_RE,relevance:0},E=t.optional(o)+e.IDENT_RE+"\\s*\\(",N={keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"],type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"],literal:"true false NULL",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr"},O=[b,a,s,e.C_BLOCK_COMMENT_MODE,g,c],C={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:N,contains:O.concat([{begin:/\(/,end:/\)/,keywords:N,contains:O.concat(["self"]),relevance:0}]),relevance:0},M={begin:"("+r+"[\\*&\\s]+)+"+E,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:N,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:d,keywords:N,relevance:0},{begin:E,returnBegin:!0,contains:[e.inherit(p,{className:"title.function"})],relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/,keywords:N,relevance:0,contains:[s,e.C_BLOCK_COMMENT_MODE,c,g,a,{begin:/\(/,end:/\)/,keywords:N,relevance:0,contains:["self",s,e.C_BLOCK_COMMENT_MODE,c,g,a]}]},a,s,e.C_BLOCK_COMMENT_MODE,b]};return{name:"C",aliases:["h"],keywords:N,disableAutodetect:!0,illegal:"=]/,contains:[{beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:b,strings:c,keywords:N}}}return De=n,De}var Le,Mn;function hr(){if(Mn)return Le;Mn=1;function n(e){const t=e.regex,s=e.COMMENT("//","$",{contains:[{begin:/\\\n/}]}),d="decltype\\(auto\\)",o="[a-zA-Z_]\\w*::",r="(?!struct)("+d+"|"+t.optional(o)+"[a-zA-Z_]\\w*"+t.optional("<[^<>]+>")+")",a={className:"type",begin:"\\b[a-z\\d_]*_t\\b"},c={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'("+"\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)"+"|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},g={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},b={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(c,{className:"string"}),{className:"string",begin:/<.*?>/},s,e.C_BLOCK_COMMENT_MODE]},p={className:"title",begin:t.optional(o)+e.IDENT_RE,relevance:0},E=t.optional(o)+e.IDENT_RE+"\\s*\\(",m=["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"],h=["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"],N=["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"],O=["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"],A={type:h,keyword:m,literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"],_type_hints:N},D={className:"function.dispatch",relevance:0,keywords:{_hint:O},begin:t.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,t.lookahead(/(<[^<>]+>|)\s*\(/))},L=[D,b,a,s,e.C_BLOCK_COMMENT_MODE,g,c],T={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:A,contains:L.concat([{begin:/\(/,end:/\)/,keywords:A,contains:L.concat(["self"]),relevance:0}]),relevance:0},I={className:"function",begin:"("+r+"[\\*&\\s]+)+"+E,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:A,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:d,keywords:A,relevance:0},{begin:E,returnBegin:!0,contains:[p],relevance:0},{begin:/::/,relevance:0},{begin:/:/,endsWithParent:!0,contains:[c,g]},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/,keywords:A,relevance:0,contains:[s,e.C_BLOCK_COMMENT_MODE,c,g,a,{begin:/\(/,end:/\)/,keywords:A,relevance:0,contains:["self",s,e.C_BLOCK_COMMENT_MODE,c,g,a]}]},a,s,e.C_BLOCK_COMMENT_MODE,b]};return{name:"C++",aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:A,illegal:"",keywords:A,contains:["self",a]},{begin:e.IDENT_RE+"::",keywords:A},{match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/],className:{1:"keyword",3:"title.class"}}])}}return Le=n,Le}var Be,Cn;function yr(){if(Cn)return Be;Cn=1;function n(e){const t=["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"],s=["public","private","protected","static","internal","protected","abstract","async","extern","override","unsafe","virtual","new","sealed","partial"],d=["default","false","null","true"],o=["abstract","as","base","break","case","catch","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","scoped","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"],i=["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"],r={keyword:o.concat(i),built_in:t,literal:d},a=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),l={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},g=e.inherit(c,{illegal:/\n/}),b={className:"subst",begin:/\{/,end:/\}/,keywords:r},p=e.inherit(b,{illegal:/\n/}),E={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},e.BACKSLASH_ESCAPE,p]},m={className:"string",begin:/\$@"/,end:'"',contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},b]},h=e.inherit(m,{illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},p]});b.contains=[m,E,c,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,l,e.C_BLOCK_COMMENT_MODE],p.contains=[h,E,g,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,l,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];const N={variants:[m,E,c,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},O={begin:"<",end:">",contains:[{beginKeywords:"in out"},a]},C=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",M={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:r,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:""},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{keyword:"if else elif endif define undef warning error line region endregion pragma checksum"}},N,l,{beginKeywords:"class interface",relevance:0,end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},a,O,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[a,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[a,O,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+C+"\\s+)+"+e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:r,contains:[{beginKeywords:s.join(" "),relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,contains:[e.TITLE_MODE,O],relevance:0},{match:/\(\)/},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:r,relevance:0,contains:[N,l,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},M]}}return Be=n,Be}var Ue,In;function Tr(){if(In)return Ue;In=1;const n=r=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:r.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:r.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],s=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],d=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse();function i(r){const a=r.regex,l=n(r),c={begin:/-(webkit|moz|ms|o)-(?=[a-z])/},g="and or not only",b=/@-?\w[\w]*(-\w+)*/,p="[a-zA-Z-][a-zA-Z0-9_-]*",E=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE];return{name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,c,l.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:"selector-class",begin:"\\."+p,relevance:0},l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{begin:":("+s.join("|")+")"},{begin:":(:)?("+d.join("|")+")"}]},l.CSS_VARIABLE,{className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/,contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...E,{begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"},contains:[...E,{className:"string",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]},l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]",relevance:0,illegal:/:/,contains:[{className:"keyword",begin:b},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:g,attribute:t.join(" ")},contains:[{begin:/[a-z-]+(?=:)/,className:"attribute"},...E,l.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}return Ue=i,Ue}var Pe,kn;function Sr(){if(kn)return Pe;kn=1;function n(e){const t=e.regex,s={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},d={begin:"^[-\\*]{3,}",end:"$"},o={className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},i={className:"bullet",begin:"^[ ]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},r={begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]},a=/[A-Za-z][A-Za-z0-9+.-]*/,l={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,relevance:2},{begin:t.concat(/\[.+?\]\(/,a,/:\/\/.*?\)/),relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}]},c={className:"strong",contains:[],variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}]},g={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{begin:/_(?![_\s])/,end:/_/,relevance:0}]},b=e.inherit(c,{contains:[]}),p=e.inherit(g,{contains:[]});c.contains.push(p),g.contains.push(b);let E=[s,l];return[c,g,b,p].forEach(N=>{N.contains=N.contains.concat(E)}),E=E.concat(c,g),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:E},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:E}]}]},s,i,c,g,{className:"quote",begin:"^>\\s+",contains:E,end:"$"},o,d,l,r]}}return Pe=n,Pe}var Fe,xn;function vr(){if(xn)return Fe;xn=1;function n(e){const t=e.regex;return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,match:t.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/)},{className:"comment",variants:[{begin:t.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/),end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,end:/$/}]}}return Fe=n,Fe}var $e,Dn;function Or(){if(Dn)return $e;Dn=1;function n(e){const t=e.regex,s="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",d=t.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),o=t.concat(d,/(::\w+)*/),r={"variable.constant":["__FILE__","__LINE__","__ENCODING__"],"variable.language":["self","super"],keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield",...["include","extend","prepend","public","private","protected","raise","throw"]],built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"],literal:["true","false","nil"]},a={className:"doctag",begin:"@[A-Za-z]+"},l={begin:"#<",end:">"},c=[e.COMMENT("#","$",{contains:[a]}),e.COMMENT("^=begin","^=end",{contains:[a],relevance:10}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],g={className:"subst",begin:/#\{/,end:/\}/,keywords:r},b={className:"string",contains:[e.BACKSLASH_ESCAPE,g],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{begin:t.concat(/<<[-~]?'?/,t.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,g]})]}]},p="[1-9](_?[0-9])*|0",E="[0-9](_?[0-9])*",m={className:"number",relevance:0,variants:[{begin:`\\b(${p})(\\.(${E}))?([eE][+-]?(${E})|r)?i?\\b`},{begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{begin:"\\b0(_?[0-7])+r?i?\\b"}]},h={variants:[{match:/\(\)/},{className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,keywords:r}]},L=[b,{variants:[{match:[/class\s+/,o,/\s+<\s+/,o]},{match:[/\b(class|module)\s+/,o]}],scope:{2:"title.class",4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,o],scope:{2:"title.class"},keywords:r},{relevance:0,match:[o,/\.new[. (]/],scope:{1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"},{relevance:0,match:d,scope:"title.class"},{match:[/def/,/\s+/,s],scope:{1:"keyword",3:"title.function"},contains:[h]},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[b,{begin:s}],relevance:0},m,{className:"variable",begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,g],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(l,c),relevance:0}].concat(l,c);g.contains=L,h.contains=L;const F=[{begin:/^\s*=>/,starts:{end:"$",contains:L}},{className:"meta.prompt",begin:"^("+"[>?]>"+"|"+"[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]"+"|"+"(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>"+")(?=[ ])",starts:{end:"$",keywords:r,contains:L}}];return c.unshift(l),{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/,contains:[e.SHEBANG({binary:"ruby"})].concat(F).concat(c).concat(L)}}return $e=n,$e}var ze,Ln;function wr(){if(Ln)return ze;Ln=1;function n(e){const i={keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"],type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"],literal:["true","false","iota","nil"],built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"]};return{name:"Go",aliases:["golang"],keywords:i,illegal:"d(i,r,a-1))}function o(i){const r=i.regex,a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",l=a+d("(?:<"+a+"~~~(?:\\s*,\\s*"+a+"~~~)*>)?",/~~~/g,2),E={keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed","yield","permits"],literal:["false","true","null"],type:["char","boolean","long","float","int","byte","short","double"],built_in:["super","this"]},m={className:"meta",begin:"@"+a,contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},h={className:"params",begin:/\(/,end:/\)/,keywords:E,relevance:0,contains:[i.C_BLOCK_COMMENT_MODE],endsParent:!0};return{name:"Java",aliases:["jsp"],keywords:E,illegal:/<\/|#/,contains:[i.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{begin:/import java\.[a-z]+\./,keywords:"import",relevance:2},i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/,className:"string",contains:[i.BACKSLASH_ESCAPE]},i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,{match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,a],className:{1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{begin:[r.concat(/(?!else)/,a),/\s+/,a,/\s+/,/=(?!=)/],className:{1:"type",3:"variable",5:"operator"}},{begin:[/record/,/\s+/,a],className:{1:"keyword",3:"title.class"},contains:[h,i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE]},{beginKeywords:"new throw return else",relevance:0},{begin:["(?:"+l+"\\s+)",i.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{2:"title.function"},keywords:E,contains:[{className:"params",begin:/\(/,end:/\)/,keywords:E,relevance:0,contains:[m,i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,s,i.C_BLOCK_COMMENT_MODE]},i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE]},s,m]}}return Ge=o,Ge}var He,Fn;function Cr(){if(Fn)return He;Fn=1;const n="[A-Za-z$_][0-9A-Za-z$_]*",e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],t=["true","false","null","undefined","NaN","Infinity"],s=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],d=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],o=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],i=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],r=[].concat(o,s,d);function a(l){const c=l.regex,g=(q,{after:K})=>{const Q="",end:""},E=/<[A-Za-z0-9\\._:-]+\s*\/>/,m={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(q,K)=>{const Q=q[0].length+q.index,j=q.input[Q];if(j==="<"||j===","){K.ignoreMatch();return}j===">"&&(g(q,{after:Q})||K.ignoreMatch());let ee;const ie=q.input.substring(Q);if(ee=ie.match(/^\s*=/)){K.ignoreMatch();return}if((ee=ie.match(/^\s+extends\s+/))&&ee.index===0){K.ignoreMatch();return}}},h={$pattern:n,keyword:e,literal:t,built_in:r,"variable.language":i},N="[0-9](_?[0-9])*",O=`\\.(${N})`,C="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",M={className:"number",variants:[{begin:`(\\b(${C})((${O})|\\.)?|(${O}))[eE][+-]?(${N})\\b`},{begin:`\\b(${C})\\b((${O})\\b|\\.)?|(${O})\\b`},{begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{begin:"\\b0[0-7]+n?\\b"}],relevance:0},A={className:"subst",begin:"\\$\\{",end:"\\}",keywords:h,contains:[]},D={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[l.BACKSLASH_ESCAPE,A],subLanguage:"xml"}},L={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[l.BACKSLASH_ESCAPE,A],subLanguage:"css"}},T={begin:"gql`",end:"",starts:{end:"`",returnEnd:!1,contains:[l.BACKSLASH_ESCAPE,A],subLanguage:"graphql"}},I={className:"string",begin:"`",end:"`",contains:[l.BACKSLASH_ESCAPE,A]},F={className:"comment",variants:[l.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,excludeBegin:!0,relevance:0},{className:"variable",begin:b+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),l.C_BLOCK_COMMENT_MODE,l.C_LINE_COMMENT_MODE]},J=[l.APOS_STRING_MODE,l.QUOTE_STRING_MODE,D,L,T,I,{match:/\$\d+/},M];A.contains=J.concat({begin:/\{/,end:/\}/,keywords:h,contains:["self"].concat(J)});const ae=[].concat(F,A.contains),W=ae.concat([{begin:/\(/,end:/\)/,keywords:h,contains:["self"].concat(ae)}]),B={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:W},V={variants:[{match:[/class/,/\s+/,b,/\s+/,/extends/,/\s+/,c.concat(b,"(",c.concat(/\./,b),")*")],scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{match:[/class/,/\s+/,b],scope:{1:"keyword",3:"title.class"}}]},u={relevance:0,match:c.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:"title.class",keywords:{_:[...s,...d]}},f={label:"use_strict",className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},w={variants:[{match:[/function/,/\s+/,b,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:"keyword",3:"title.function"},label:"func.def",contains:[B],illegal:/%/},k={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"};function U(q){return c.concat("(?!",q.join("|"),")")}const Y={match:c.concat(/\b/,U([...o,"super","import"]),b,c.lookahead(/\(/)),className:"title.function",relevance:0},ne={begin:c.concat(/\./,c.lookahead(c.concat(b,/(?![0-9A-Za-z$_(])/))),end:b,excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},te={match:[/get|set/,/\s+/,b,/(?=\()/],className:{1:"keyword",3:"title.function"},contains:[{begin:/\(\)/},B]},z="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+l.UNDERSCORE_IDENT_RE+")\\s*=>",Z={match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,c.lookahead(z)],keywords:"async",className:{1:"keyword",3:"title.function"},contains:[B]};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:h,exports:{PARAMS_CONTAINS:W,CLASS_REFERENCE:u},illegal:/#(?![$_A-z])/,contains:[l.SHEBANG({label:"shebang",binary:"node",relevance:5}),f,l.APOS_STRING_MODE,l.QUOTE_STRING_MODE,D,L,T,I,F,{match:/\$\d+/},M,u,{className:"attr",begin:b+c.lookahead(":"),relevance:0},Z,{begin:"("+l.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",relevance:0,contains:[F,l.REGEXP_MODE,{className:"function",begin:z,returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:l.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:W}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:p.begin,end:p.end},{match:E},{begin:m.begin,"on:begin":m.isTrulyOpeningTag,end:m.end}],subLanguage:"xml",contains:[{begin:m.begin,end:m.end,skip:!0,contains:["self"]}]}]},w,{beginKeywords:"while if switch catch for"},{begin:"\\b(?!function)"+l.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,label:"func.def",contains:[B,l.inherit(l.TITLE_MODE,{begin:b,className:"title.function"})]},{match:/\.\.\./,relevance:0},ne,{match:"\\$"+b,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},contains:[B]},Y,k,V,te,{match:/\$[(.]/}]}}return He=a,He}var We,$n;function Ir(){if($n)return We;$n=1;function n(e){const t={className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},s={match:/[{}[\],:]/,className:"punctuation",relevance:0},d=["true","false","null"],o={scope:"literal",beginKeywords:d.join(" ")};return{name:"JSON",keywords:{literal:d},contains:[t,s,e.QUOTE_STRING_MODE,o,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:"\\S"}}return We=n,We}var Ye,zn;function kr(){if(zn)return Ye;zn=1;var n="[0-9](_*[0-9])*",e=`\\.(${n})`,t="[0-9a-fA-F](_*[0-9a-fA-F])*",s={className:"number",variants:[{begin:`(\\b(${n})((${e})|\\.)?|(${e}))[eE][+-]?(${n})[fFdD]?\\b`},{begin:`\\b(${n})((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{begin:`(${e})[fFdD]?\\b`},{begin:`\\b(${n})[fFdD]\\b`},{begin:`\\b0[xX]((${t})\\.?|(${t})?\\.(${t}))[pP][+-]?(${n})[fFdD]?\\b`},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${t})[lL]?\\b`},{begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],relevance:0};function d(o){const i={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},r={className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a={className:"symbol",begin:o.UNDERSCORE_IDENT_RE+"@"},l={className:"subst",begin:/\$\{/,end:/\}/,contains:[o.C_NUMBER_MODE]},c={className:"variable",begin:"\\$"+o.UNDERSCORE_IDENT_RE},g={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[c,l]},{begin:"'",end:"'",illegal:/\n/,contains:[o.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[o.BACKSLASH_ESCAPE,c,l]}]};l.contains.push(g);const b={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+o.UNDERSCORE_IDENT_RE+")?"},p={className:"meta",begin:"@"+o.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[o.inherit(g,{className:"string"}),"self"]}]},E=s,m=o.COMMENT("/\\*","\\*/",{contains:[o.C_BLOCK_COMMENT_MODE]}),h={variants:[{className:"type",begin:o.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},N=h;return N.variants[1].contains=[h],h.variants[1].contains=[N],{name:"Kotlin",aliases:["kt","kts"],keywords:i,contains:[o.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),o.C_LINE_COMMENT_MODE,m,r,a,b,p,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:i,relevance:5,contains:[{begin:o.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[o.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[h,o.C_LINE_COMMENT_MODE,m],relevance:0},o.C_LINE_COMMENT_MODE,m,b,p,g,o.C_NUMBER_MODE]},m]},{begin:[/class|interface|trait/,/\s+/,o.UNDERSCORE_IDENT_RE],beginScope:{3:"title.class"},keywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},o.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,){\s]|$/,excludeBegin:!0,returnEnd:!0},b,p]},g,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:` +`},E]}}return Ye=d,Ye}var Ze,Kn;function xr(){if(Kn)return Ze;Kn=1;const n=a=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:a.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:a.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],s=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],d=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse(),i=s.concat(d);function r(a){const l=n(a),c=i,g="and or not only",b="[\\w-]+",p="("+b+"|@\\{"+b+"\\})",E=[],m=[],h=function(F){return{className:"string",begin:"~?"+F+".*?"+F}},N=function(F,J,ae){return{className:F,begin:J,relevance:ae}},O={$pattern:/[a-z-]+/,keyword:g,attribute:t.join(" ")},C={begin:"\\(",end:"\\)",contains:m,keywords:O,relevance:0};m.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,h("'"),h('"'),l.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},l.HEXCOLOR,C,N("variable","@@?"+b,10),N("variable","@\\{"+b+"\\}"),N("built_in","~?`[^`]*?`"),{className:"attribute",begin:b+"\\s*:",end:":",returnBegin:!0,excludeEnd:!0},l.IMPORTANT,{beginKeywords:"and not"},l.FUNCTION_DISPATCH);const M=m.concat({begin:/\{/,end:/\}/,contains:E}),A={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(m)},D={begin:p+"\\s*:",returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/},l.CSS_VARIABLE,{className:"attribute",begin:"\\b("+o.join("|")+")\\b",end:/(?=:)/,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:m}}]},L={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",keywords:O,returnEnd:!0,contains:m,relevance:0}},T={className:"variable",variants:[{begin:"@"+b+"\\s*:",relevance:15},{begin:"@"+b}],starts:{end:"[;}]",returnEnd:!0,contains:M}},I={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:p,end:/\{/}],returnBegin:!0,returnEnd:!0,illegal:`[<='$"]`,relevance:0,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,A,N("keyword","all\\b"),N("variable","@\\{"+b+"\\}"),{begin:"\\b("+e.join("|")+")\\b",className:"selector-tag"},l.CSS_NUMBER_MODE,N("selector-tag",p,0),N("selector-id","#"+p),N("selector-class","\\."+p,0),N("selector-tag","&",0),l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",begin:":("+s.join("|")+")"},{className:"selector-pseudo",begin:":(:)?("+d.join("|")+")"},{begin:/\(/,end:/\)/,relevance:0,contains:M},{begin:"!important"},l.FUNCTION_DISPATCH]},H={begin:b+`:(:)?(${c.join("|")})`,returnBegin:!0,contains:[I]};return E.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,L,T,H,D,I,A,l.FUNCTION_DISPATCH),{name:"Less",case_insensitive:!0,illegal:`[=>'/<($"]`,contains:E}}return Ze=r,Ze}var Xe,qn;function Dr(){if(qn)return Xe;qn=1;function n(e){const t="\\[=*\\[",s="\\]=*\\]",d={begin:t,end:s,contains:["self"]},o=[e.COMMENT("--(?!"+t+")","$"),e.COMMENT("--"+t,s,{contains:[d],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:o.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:o}].concat(o)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:t,end:s,contains:[d],relevance:5}])}}return Xe=n,Xe}var Ve,Gn;function Lr(){if(Gn)return Ve;Gn=1;function n(e){const t={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%\{/,end:/\}/},a={variants:[{begin:/\$\d/},{begin:t.concat(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])")},{begin:/[$%@][^\s\w{]/,relevance:0}]},l=[e.BACKSLASH_ESCAPE,i,a],c=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],g=(E,m,h="\\1")=>{const N=h==="\\1"?h:t.concat(h,m);return t.concat(t.concat("(?:",E,")"),m,/(?:\\.|[^\\\/])*?/,N,/(?:\\.|[^\\\/])*?/,h,d)},b=(E,m,h)=>t.concat(t.concat("(?:",E,")"),m,/(?:\\.|[^\\\/])*?/,h,d),p=[a,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{endsWithParent:!0}),r,{className:"string",contains:l,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{begin:g("s|tr|y",t.either(...c,{capture:!0}))},{begin:g("s|tr|y","\\(","\\)")},{begin:g("s|tr|y","\\[","\\]")},{begin:g("s|tr|y","\\{","\\}")}],relevance:2},{className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{begin:b("(?:m|qr)?",/\//,/\//)},{begin:b("m|qr",t.either(...c,{capture:!0}),/\1/)},{begin:b("m|qr",/\(/,/\)/)},{begin:b("m|qr",/\[/,/\]/)},{begin:b("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return i.contains=p,r.contains=p,{name:"Perl",aliases:["pl","pm"],keywords:o,contains:p}}return Qe=n,Qe}var Je,Wn;function Ur(){if(Wn)return Je;Wn=1;function n(e){const t={className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},s=/[a-zA-Z@][a-zA-Z0-9_]*/,a={"variable.language":["this","super"],$pattern:s,keyword:["while","export","sizeof","typedef","const","struct","for","union","volatile","static","mutable","if","do","return","goto","enum","else","break","extern","asm","case","default","register","explicit","typename","switch","continue","inline","readonly","assign","readwrite","self","@synchronized","id","typeof","nonatomic","IBOutlet","IBAction","strong","weak","copy","in","out","inout","bycopy","byref","oneway","__strong","__weak","__block","__autoreleasing","@private","@protected","@public","@try","@property","@end","@throw","@catch","@finally","@autoreleasepool","@synthesize","@dynamic","@selector","@optional","@required","@encode","@package","@import","@defs","@compatibility_alias","__bridge","__bridge_transfer","__bridge_retained","__bridge_retain","__covariant","__contravariant","__kindof","_Nonnull","_Nullable","_Null_unspecified","__FUNCTION__","__PRETTY_FUNCTION__","__attribute__","getter","setter","retain","unsafe_unretained","nonnull","nullable","null_unspecified","null_resettable","class","instancetype","NS_DESIGNATED_INITIALIZER","NS_UNAVAILABLE","NS_REQUIRES_SUPER","NS_RETURNS_INNER_POINTER","NS_INLINE","NS_AVAILABLE","NS_DEPRECATED","NS_ENUM","NS_OPTIONS","NS_SWIFT_UNAVAILABLE","NS_ASSUME_NONNULL_BEGIN","NS_ASSUME_NONNULL_END","NS_REFINED_FOR_SWIFT","NS_SWIFT_NAME","NS_SWIFT_NOTHROW","NS_DURING","NS_HANDLER","NS_ENDHANDLER","NS_VALUERETURN","NS_VOIDRETURN"],literal:["false","true","FALSE","TRUE","nil","YES","NO","NULL"],built_in:["dispatch_once_t","dispatch_queue_t","dispatch_sync","dispatch_async","dispatch_once"],type:["int","float","char","unsigned","signed","short","long","double","wchar_t","unichar","void","bool","BOOL","id|0","_Bool"]},l={$pattern:s,keyword:["@interface","@class","@protocol","@implementation"]};return{name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"],keywords:a,illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+l.keyword.join("|")+")\\b",end:/(\{|$)/,excludeEnd:!0,keywords:l,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}return Je=n,Je}var je,Yn;function Pr(){if(Yn)return je;Yn=1;function n(e){const t=e.regex,s=/(?![A-Za-z0-9])(?![$])/,d=t.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,s),o=t.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,s),i={scope:"variable",match:"\\$+"+d},r={scope:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{begin:/\?>/}]},a={scope:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]},l=e.inherit(e.APOS_STRING_MODE,{illegal:null}),c=e.inherit(e.QUOTE_STRING_MODE,{illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(a)}),g={begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/,contains:e.QUOTE_STRING_MODE.contains.concat(a),"on:begin":(W,B)=>{B.data._beginMatch=W[1]||W[2]},"on:end":(W,B)=>{B.data._beginMatch!==W[1]&&B.ignoreMatch()}},b=e.END_SAME_AS_BEGIN({begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/}),p=`[ +]`,E={scope:"string",variants:[c,l,g,b]},m={scope:"number",variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"}],relevance:0},h=["false","null","true"],N=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],O=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],M={keyword:N,literal:(W=>{const B=[];return W.forEach(V=>{B.push(V),V.toLowerCase()===V?B.push(V.toUpperCase()):B.push(V.toLowerCase())}),B})(h),built_in:O},A=W=>W.map(B=>B.replace(/\|\d+$/,"")),D={variants:[{match:[/new/,t.concat(p,"+"),t.concat("(?!",A(O).join("\\b|"),"\\b)"),o],scope:{1:"keyword",4:"title.class"}}]},L=t.concat(d,"\\b(?!\\()"),T={variants:[{match:[t.concat(/::/,t.lookahead(/(?!class\b)/)),L],scope:{2:"variable.constant"}},{match:[/::/,/class/],scope:{2:"variable.language"}},{match:[o,t.concat(/::/,t.lookahead(/(?!class\b)/)),L],scope:{1:"title.class",3:"variable.constant"}},{match:[o,t.concat("::",t.lookahead(/(?!class\b)/))],scope:{1:"title.class"}},{match:[o,/::/,/class/],scope:{1:"title.class",3:"variable.language"}}]},I={scope:"attr",match:t.concat(d,t.lookahead(":"),t.lookahead(/(?!::)/))},H={relevance:0,begin:/\(/,end:/\)/,keywords:M,contains:[I,i,T,e.C_BLOCK_COMMENT_MODE,E,m,D]},F={relevance:0,match:[/\b/,t.concat("(?!fn\\b|function\\b|",A(N).join("\\b|"),"|",A(O).join("\\b|"),"\\b)"),d,t.concat(p,"*"),t.lookahead(/(?=\()/)],scope:{3:"title.function.invoke"},contains:[H]};H.contains.push(F);const J=[I,T,e.C_BLOCK_COMMENT_MODE,E,m,D],ae={begin:t.concat(/#\[\s*/,o),beginScope:"meta",end:/]/,endScope:"meta",keywords:{literal:h,keyword:["new","array"]},contains:[{begin:/\[/,end:/]/,keywords:{literal:h,keyword:["new","array"]},contains:["self",...J]},...J,{scope:"meta",match:o}]};return{case_insensitive:!1,keywords:M,contains:[ae,e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},r,{scope:"variable.language",match:/\$this\b/},i,F,T,{match:[/const/,/\s/,d],scope:{1:"keyword",3:"variable.constant"}},D,{scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"},e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:M,contains:["self",i,T,e.C_BLOCK_COMMENT_MODE,E,m]}]},{scope:"class",variants:[{beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait",illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/,contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{beginKeywords:"use",relevance:0,end:";",contains:[{match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},E,m]}}return je=n,je}var en,Zn;function Fr(){if(Zn)return en;Zn=1;function n(e){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}return en=n,en}var nn,Xn;function $r(){if(Xn)return nn;Xn=1;function n(e){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}return nn=n,nn}var tn,Vn;function zr(){if(Vn)return tn;Vn=1;function n(e){const t=e.regex,s=new RegExp("[\\p{XID_Start}_]\\p{XID_Continue}*","u"),d=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],a={$pattern:/[A-Za-z]\w+|__\w+__/,keyword:d,built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"],literal:["__debug__","Ellipsis","False","None","NotImplemented","True"],type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"]},l={className:"meta",begin:/^(>>>|\.\.\.) /},c={className:"subst",begin:/\{/,end:/\}/,keywords:a,illegal:/#/},g={begin:/\{\{/,relevance:0},b={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,l],relevance:10},{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,l],relevance:10},{begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,l,g,c]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,l,g,c]},{begin:/([uU]|[rR])'/,end:/'/,relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/,end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,g,c]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,g,c]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},p="[0-9](_?[0-9])*",E=`(\\b(${p}))?\\.(${p})|\\b(${p})\\.`,m=`\\b|${d.join("|")}`,h={className:"number",relevance:0,variants:[{begin:`(\\b(${p})|(${E}))[eE][+-]?(${p})[jJ]?(?=${m})`},{begin:`(${E})[jJ]?`},{begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${m})`},{begin:`\\b0[bB](_?[01])+[lL]?(?=${m})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${m})`},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${m})`},{begin:`\\b(${p})[jJ](?=${m})`}]},N={className:"comment",begin:t.lookahead(/# type:/),end:/$/,keywords:a,contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},O={className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:a,contains:["self",l,h,b,e.HASH_COMMENT_MODE]}]};return c.contains=[b,h,l],{name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:a,illegal:/(<\/|\?)|=>/,contains:[l,h,{begin:/\bself\b/},{beginKeywords:"if",relevance:0},b,N,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,s],scope:{1:"keyword",3:"title.function"},contains:[O]},{variants:[{match:[/\bclass/,/\s+/,s,/\s*/,/\(\s*/,s,/\s*\)/]},{match:[/\bclass/,/\s+/,s]}],scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[h,O,b]}]}}return tn=n,tn}var rn,Qn;function Kr(){if(Qn)return rn;Qn=1;function n(e){return{aliases:["pycon"],contains:[{className:"meta.prompt",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}return rn=n,rn}var an,Jn;function qr(){if(Jn)return an;Jn=1;function n(e){const t=e.regex,s=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,d=t.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),o=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,i=t.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/);return{name:"R",keywords:{$pattern:s,keyword:"function if in break next repeat else for while",literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10",built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm"},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/,starts:{end:t.lookahead(t.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)),endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{scope:"variable",variants:[{match:s},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0}]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}]}),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE],variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"',relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{1:"operator",2:"number"},match:[o,d]},{scope:{1:"operator",2:"number"},match:[/%[^%]*%/,d]},{scope:{1:"punctuation",2:"number"},match:[i,d]},{scope:{2:"number"},match:[/[^a-zA-Z0-9._]|^/,d]}]},{scope:{3:"operator"},match:[s,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:o},{match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:i},{begin:"`",end:"`",contains:[{begin:/\\./}]}]}}return an=n,an}var sn,jn;function Gr(){if(jn)return sn;jn=1;function n(e){const t=e.regex,s={className:"title.function.invoke",relevance:0,begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,t.lookahead(/\s*\(/))},d="([ui](8|16|32|64|128|size)|f(32|64))?",o=["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"],i=["true","false","Some","None","Ok","Err"],r=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],a=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"];return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:a,keyword:o,literal:i,built_in:r},illegal:""},s]}}return sn=n,sn}var on,et;function Hr(){if(et)return on;et=1;const n=r=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:r.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:r.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],s=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],d=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse();function i(r){const a=n(r),l=d,c=s,g="@[a-z-]+",b="and or not only",E={className:"variable",begin:"(\\$"+"[a-zA-Z-][a-zA-Z0-9_-]*"+")\\b",relevance:0};return{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,a.CSS_NUMBER_MODE,{className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag",begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo",begin:":("+c.join("|")+")"},{className:"selector-pseudo",begin:":(:)?("+l.join("|")+")"},E,{begin:/\(/,end:/\)/,contains:[a.CSS_NUMBER_MODE]},a.CSS_VARIABLE,{className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:/:/,end:/[;}{]/,relevance:0,contains:[a.BLOCK_COMMENT,E,a.HEXCOLOR,a.CSS_NUMBER_MODE,r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,a.IMPORTANT,a.FUNCTION_DISPATCH]},{begin:"@(page|font-face)",keywords:{$pattern:g,keyword:"@page @font-face"}},{begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,keyword:b,attribute:t.join(" ")},contains:[{begin:g,className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute"},E,r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,a.HEXCOLOR,a.CSS_NUMBER_MODE]},a.FUNCTION_DISPATCH]}}return on=i,on}var cn,nt;function Wr(){if(nt)return cn;nt=1;function n(e){return{name:"Shell Session",aliases:["console","shellsession"],contains:[{className:"meta.prompt",begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,subLanguage:"bash"}}]}}return cn=n,cn}var ln,tt;function Yr(){if(tt)return ln;tt=1;function n(e){const t=e.regex,s=e.COMMENT("--","$"),d={className:"string",variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},o={begin:/"/,end:/"/,contains:[{begin:/""/}]},i=["true","false","unknown"],r=["double precision","large object","with timezone","without timezone"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],l=["add","asc","collation","desc","final","first","last","view"],c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year"],g=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],b=["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"],p=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],E=g,m=[...c,...l].filter(M=>!g.includes(M)),h={className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},N={className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0},O={begin:t.concat(/\b/,t.either(...E),/\s*\(/),relevance:0,keywords:{built_in:E}};function C(M,{exceptions:A,when:D}={}){const L=D;return A=A||[],M.map(T=>T.match(/\|\d+$/)||A.includes(T)?T:L(T)?`${T}|0`:T)}return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/,keyword:C(m,{when:M=>M.length<3}),literal:i,type:a,built_in:b},contains:[{begin:t.either(...p),relevance:0,keywords:{$pattern:/[\w\.]+/,keyword:m.concat(p),literal:i,type:a}},{className:"type",begin:t.either(...r)},O,h,d,o,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,s,N]}}return ln=n,ln}var dn,rt;function Zr(){if(rt)return dn;rt=1;function n(T){return T?typeof T=="string"?T:T.source:null}function e(T){return t("(?=",T,")")}function t(...T){return T.map(H=>n(H)).join("")}function s(T){const I=T[T.length-1];return typeof I=="object"&&I.constructor===Object?(T.splice(T.length-1,1),I):{}}function d(...T){return"("+(s(T).capture?"":"?:")+T.map(F=>n(F)).join("|")+")"}const o=T=>t(/\b/,T,/\w$/.test(T)?/\b/:/\B/),i=["Protocol","Type"].map(o),r=["init","self"].map(o),a=["Any","Self"],l=["actor","any","associatedtype","async","await",/as\?/,/as!/,"as","borrowing","break","case","catch","class","consume","consuming","continue","convenience","copy","default","defer","deinit","didSet","distributed","do","dynamic","each","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","isolated","nonisolated","lazy","let","macro","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],c=["false","nil","true"],g=["assignment","associativity","higherThan","left","lowerThan","none","right"],b=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warning"],p=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],E=d(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),m=d(E,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),h=t(E,m,"*"),N=d(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),O=d(N,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),C=t(N,O,"*"),M=t(/[A-Z]/,O,"*"),A=["attached","autoclosure",t(/convention\(/,d("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","freestanding","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",t(/objc\(/,C,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","resultBuilder","Sendable","testable","UIApplicationMain","unchecked","unknown","usableFromInline","warn_unqualified_access"],D=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"];function L(T){const I={match:/\s+/,relevance:0},H=T.COMMENT("/\\*","\\*/",{contains:["self"]}),F=[T.C_LINE_COMMENT_MODE,H],J={match:[/\./,d(...i,...r)],className:{2:"keyword"}},ae={match:t(/\./,d(...l)),relevance:0},W=l.filter($=>typeof $=="string").concat(["_|0"]),B=l.filter($=>typeof $!="string").concat(a).map(o),V={variants:[{className:"keyword",match:d(...B,...r)}]},u={$pattern:d(/\b\w+/,/#\w+/),keyword:W.concat(b),literal:c},f=[J,ae,V],w={match:t(/\./,d(...p)),relevance:0},k={className:"built_in",match:t(/\b/,d(...p),/(?=\()/)},U=[w,k],Y={match:/->/,relevance:0},ne={className:"operator",relevance:0,variants:[{match:h},{match:`\\.(\\.|${m})+`}]},te=[Y,ne],z="([0-9]_*)+",Z="([0-9a-fA-F]_*)+",q={className:"number",relevance:0,variants:[{match:`\\b(${z})(\\.(${z}))?([eE][+-]?(${z}))?\\b`},{match:`\\b0x(${Z})(\\.(${Z}))?([pP][+-]?(${z}))?\\b`},{match:/\b0o([0-7]_*)+\b/},{match:/\b0b([01]_*)+\b/}]},K=($="")=>({className:"subst",variants:[{match:t(/\\/,$,/[0\\tnr"']/)},{match:t(/\\/,$,/u\{[0-9a-fA-F]{1,8}\}/)}]}),Q=($="")=>({className:"subst",match:t(/\\/,$,/[\t ]*(?:[\r\n]|\r\n)/)}),j=($="")=>({className:"subst",label:"interpol",begin:t(/\\/,$,/\(/),end:/\)/}),ee=($="")=>({begin:t($,/"""/),end:t(/"""/,$),contains:[K($),Q($),j($)]}),ie=($="")=>({begin:t($,/"/),end:t(/"/,$),contains:[K($),j($)]}),oe={className:"string",variants:[ee(),ee("#"),ee("##"),ee("###"),ie(),ie("#"),ie("##"),ie("###")]},ge=[T.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[T.BACKSLASH_ESCAPE]}],Te={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//,contains:ge},re=$=>{const Oe=t($,/\//),Ne=t(/\//,$);return{begin:Oe,end:Ne,contains:[...ge,{scope:"comment",begin:`#(?!.*${Ne})`,end:/$/}]}},Se={scope:"regexp",variants:[re("###"),re("##"),re("#"),Te]},_e={match:t(/`/,C,/`/)},S={className:"variable",match:/\$\d+/},ve={className:"variable",match:`\\$${O}+`},G=[_e,S,ve],P={match:/(@|#(un)?)available/,scope:"keyword",starts:{contains:[{begin:/\(/,end:/\)/,keywords:D,contains:[...te,q,oe]}]}},Ee={scope:"keyword",match:t(/@/,d(...A))},le={scope:"meta",match:t(/@/,C)},de=[P,Ee,le],ce={match:e(/\b[A-Z]/),relevance:0,contains:[{className:"type",match:t(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,O,"+")},{className:"type",match:M,relevance:0},{match:/[?!]+/,relevance:0},{match:/\.\.\./,relevance:0},{match:t(/\s+&\s+/,e(M)),relevance:0}]},_={begin://,keywords:u,contains:[...F,...f,...de,Y,ce]};ce.contains.push(_);const y={match:t(C,/\s*:/),keywords:"_|0",relevance:0},v={begin:/\(/,end:/\)/,relevance:0,keywords:u,contains:["self",y,...F,Se,...f,...U,...te,q,oe,...G,...de,ce]},x={begin://,keywords:"repeat each",contains:[...F,ce]},X={begin:d(e(t(C,/\s*:/)),e(t(C,/\s+/,C,/\s*:/))),end:/:/,relevance:0,contains:[{className:"keyword",match:/\b_\b/},{className:"params",match:C}]},se={begin:/\(/,end:/\)/,keywords:u,contains:[X,...F,...f,...te,q,oe,...de,ce,v],endsParent:!0,illegal:/["']/},Me={match:[/(func|macro)/,/\s+/,d(_e.match,C,h)],className:{1:"keyword",3:"title.function"},contains:[x,se,I],illegal:[/\[/,/%/]},Ce={match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:"keyword"},contains:[x,se,I],illegal:/\[|%/},ht={match:[/operator/,/\s+/,h],className:{1:"keyword",3:"title"}},yt={begin:[/precedencegroup/,/\s+/,M],className:{1:"keyword",3:"title"},contains:[ce],keywords:[...g,...c],end:/}/};for(const $ of oe.variants){const Oe=$.contains.find(Tt=>Tt.label==="interpol");Oe.keywords=u;const Ne=[...f,...U,...te,q,oe,...G];Oe.contains=[...Ne,{begin:/\(/,end:/\)/,contains:["self",...Ne]}]}return{name:"Swift",keywords:u,contains:[...F,Me,Ce,{beginKeywords:"struct protocol class extension enum actor",end:"\\{",excludeEnd:!0,keywords:u,contains:[T.inherit(T.TITLE_MODE,{className:"title.class",begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...f]},ht,yt,{beginKeywords:"import",end:/$/,contains:[...F],relevance:0},Se,...f,...U,...te,q,oe,...G,...de,ce,v]}}return dn=L,dn}var un,at;function Xr(){if(at)return un;at=1;function n(e){const t="true false yes no null",s="[\\w#;/?:@&=+$,.~*'()[\\]]+",d={className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ ]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ ]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ ]|$)"}]},o={className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]},i={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,o]},r=e.inherit(i,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),b={className:"number",begin:"\\b"+"[0-9]{4}(-[0-9][0-9]){0,2}"+"([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?"+"(\\.[0-9]*)?"+"([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?"+"\\b"},p={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},E={begin:/\{/,end:/\}/,contains:[p],illegal:"\\n",relevance:0},m={begin:"\\[",end:"\\]",contains:[p],illegal:"\\n",relevance:0},h=[d,{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+s},{className:"type",begin:"!<"+s+">"},{className:"type",begin:"!"+s},{className:"type",begin:"!!"+s},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},b,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},E,m,i],N=[...h];return N.pop(),N.push(r),p.contains=N,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:h}}return un=n,un}var gn,it;function Vr(){if(it)return gn;it=1;const n="[A-Za-z$_][0-9A-Za-z$_]*",e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],t=["true","false","null","undefined","NaN","Infinity"],s=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],d=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],o=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],i=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],r=[].concat(o,s,d);function a(c){const g=c.regex,b=(K,{after:Q})=>{const j="",end:""},m=/<[A-Za-z0-9\\._:-]+\s*\/>/,h={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(K,Q)=>{const j=K[0].length+K.index,ee=K.input[j];if(ee==="<"||ee===","){Q.ignoreMatch();return}ee===">"&&(b(K,{after:j})||Q.ignoreMatch());let ie;const oe=K.input.substring(j);if(ie=oe.match(/^\s*=/)){Q.ignoreMatch();return}if((ie=oe.match(/^\s+extends\s+/))&&ie.index===0){Q.ignoreMatch();return}}},N={$pattern:n,keyword:e,literal:t,built_in:r,"variable.language":i},O="[0-9](_?[0-9])*",C=`\\.(${O})`,M="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",A={className:"number",variants:[{begin:`(\\b(${M})((${C})|\\.)?|(${C}))[eE][+-]?(${O})\\b`},{begin:`\\b(${M})\\b((${C})\\b|\\.)?|(${C})\\b`},{begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{begin:"\\b0[0-7]+n?\\b"}],relevance:0},D={className:"subst",begin:"\\$\\{",end:"\\}",keywords:N,contains:[]},L={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,D],subLanguage:"xml"}},T={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,D],subLanguage:"css"}},I={begin:"gql`",end:"",starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,D],subLanguage:"graphql"}},H={className:"string",begin:"`",end:"`",contains:[c.BACKSLASH_ESCAPE,D]},J={className:"comment",variants:[c.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,excludeBegin:!0,relevance:0},{className:"variable",begin:p+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),c.C_BLOCK_COMMENT_MODE,c.C_LINE_COMMENT_MODE]},ae=[c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,L,T,I,H,{match:/\$\d+/},A];D.contains=ae.concat({begin:/\{/,end:/\}/,keywords:N,contains:["self"].concat(ae)});const W=[].concat(J,D.contains),B=W.concat([{begin:/\(/,end:/\)/,keywords:N,contains:["self"].concat(W)}]),V={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:N,contains:B},u={variants:[{match:[/class/,/\s+/,p,/\s+/,/extends/,/\s+/,g.concat(p,"(",g.concat(/\./,p),")*")],scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{match:[/class/,/\s+/,p],scope:{1:"keyword",3:"title.class"}}]},f={relevance:0,match:g.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:"title.class",keywords:{_:[...s,...d]}},w={label:"use_strict",className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},k={variants:[{match:[/function/,/\s+/,p,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:"keyword",3:"title.function"},label:"func.def",contains:[V],illegal:/%/},U={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"};function Y(K){return g.concat("(?!",K.join("|"),")")}const ne={match:g.concat(/\b/,Y([...o,"super","import"]),p,g.lookahead(/\(/)),className:"title.function",relevance:0},te={begin:g.concat(/\./,g.lookahead(g.concat(p,/(?![0-9A-Za-z$_(])/))),end:p,excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},z={match:[/get|set/,/\s+/,p,/(?=\()/],className:{1:"keyword",3:"title.function"},contains:[{begin:/\(\)/},V]},Z="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+c.UNDERSCORE_IDENT_RE+")\\s*=>",q={match:[/const|var|let/,/\s+/,p,/\s*/,/=\s*/,/(async\s*)?/,g.lookahead(Z)],keywords:"async",className:{1:"keyword",3:"title.function"},contains:[V]};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:N,exports:{PARAMS_CONTAINS:B,CLASS_REFERENCE:f},illegal:/#(?![$_A-z])/,contains:[c.SHEBANG({label:"shebang",binary:"node",relevance:5}),w,c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,L,T,I,H,J,{match:/\$\d+/},A,f,{className:"attr",begin:p+g.lookahead(":"),relevance:0},q,{begin:"("+c.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",relevance:0,contains:[J,c.REGEXP_MODE,{className:"function",begin:Z,returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:c.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:N,contains:B}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:E.begin,end:E.end},{match:m},{begin:h.begin,"on:begin":h.isTrulyOpeningTag,end:h.end}],subLanguage:"xml",contains:[{begin:h.begin,end:h.end,skip:!0,contains:["self"]}]}]},k,{beginKeywords:"while if switch catch for"},{begin:"\\b(?!function)"+c.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,label:"func.def",contains:[V,c.inherit(c.TITLE_MODE,{begin:p,className:"title.function"})]},{match:/\.\.\./,relevance:0},te,{match:"\\$"+p,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},contains:[V]},ne,U,u,z,{match:/\$[(.]/}]}}function l(c){const g=a(c),b=n,p=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],E={beginKeywords:"namespace",end:/\{/,excludeEnd:!0,contains:[g.exports.CLASS_REFERENCE]},m={beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:{keyword:"interface extends",built_in:p},contains:[g.exports.CLASS_REFERENCE]},h={className:"meta",relevance:10,begin:/^\s*['"]use strict['"]/},N=["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"],O={$pattern:n,keyword:e.concat(N),literal:t,built_in:r.concat(p),"variable.language":i},C={className:"meta",begin:"@"+b},M=(D,L,T)=>{const I=D.contains.findIndex(H=>H.label===L);if(I===-1)throw new Error("can not find mode to replace");D.contains.splice(I,1,T)};Object.assign(g.keywords,O),g.exports.PARAMS_CONTAINS.push(C),g.contains=g.contains.concat([C,E,m]),M(g,"shebang",c.SHEBANG()),M(g,"use_strict",h);const A=g.contains.find(D=>D.label==="func.def");return A.relevance=0,Object.assign(g,{name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),g}return gn=l,gn}var bn,st;function Qr(){if(st)return bn;st=1;function n(e){const t=e.regex,s={className:"string",begin:/"(""|[^/n])"C\b/},d={className:"string",begin:/"/,end:/"/,illegal:/\n/,contains:[{begin:/""/}]},o=/\d{1,2}\/\d{1,2}\/\d{4}/,i=/\d{4}-\d{1,2}-\d{1,2}/,r=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,a=/\d{1,2}(:\d{1,2}){1,2}/,l={className:"literal",variants:[{begin:t.concat(/# */,t.either(i,o),/ *#/)},{begin:t.concat(/# */,a,/ *#/)},{begin:t.concat(/# */,r,/ *#/)},{begin:t.concat(/# */,t.either(i,o),/ +/,t.either(r,a),/ *#/)}]},c={className:"number",relevance:0,variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},g={className:"label",begin:/^\w+:/},b=e.COMMENT(/'''/,/$/,{contains:[{className:"doctag",begin:/<\/?/,end:/>/}]}),p=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]});return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0,classNameAliases:{label:"symbol"},keywords:{keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield",built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort",type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort",literal:"true false nothing"},illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[s,d,l,c,g,b,p,{className:"meta",begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/,end:/$/,keywords:{keyword:"const disable else elseif enable end externalsource if region then"},contains:[p]}]}}return bn=n,bn}var pn,ot;function Jr(){if(ot)return pn;ot=1;function n(e){e.regex;const t=e.COMMENT(/\(;/,/;\)/);t.contains.push("self");const s=e.COMMENT(/;;/,/$/),d=["anyfunc","block","br","br_if","br_table","call","call_indirect","data","drop","elem","else","end","export","func","global.get","global.set","local.get","local.set","local.tee","get_global","get_local","global","if","import","local","loop","memory","memory.grow","memory.size","module","mut","nop","offset","param","result","return","select","set_global","set_local","start","table","tee_local","then","type","unreachable"],o={begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:"keyword",3:"title.function"}},i={className:"variable",begin:/\$[\w_]+/},r={match:/(\((?!;)|\))+/,className:"punctuation",relevance:0},a={className:"number",relevance:0,match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/},l={match:/(i32|i64|f32|f64)(?!\.)/,className:"type"},c={className:"keyword",match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/};return{name:"WebAssembly",keywords:{$pattern:/[\w.]+/,keyword:d},contains:[s,t,{match:[/(?:offset|align)/,/\s*/,/=/],className:{1:"keyword",3:"operator"}},i,r,o,e.QUOTE_STRING_MODE,l,c,a]}}return pn=n,pn}var R=Er;R.registerLanguage("xml",mr());R.registerLanguage("bash",fr());R.registerLanguage("c",Nr());R.registerLanguage("cpp",hr());R.registerLanguage("csharp",yr());R.registerLanguage("css",Tr());R.registerLanguage("markdown",Sr());R.registerLanguage("diff",vr());R.registerLanguage("ruby",Or());R.registerLanguage("go",wr());R.registerLanguage("graphql",Rr());R.registerLanguage("ini",Ar());R.registerLanguage("java",Mr());R.registerLanguage("javascript",Cr());R.registerLanguage("json",Ir());R.registerLanguage("kotlin",kr());R.registerLanguage("less",xr());R.registerLanguage("lua",Dr());R.registerLanguage("makefile",Lr());R.registerLanguage("perl",Br());R.registerLanguage("objectivec",Ur());R.registerLanguage("php",Pr());R.registerLanguage("php-template",Fr());R.registerLanguage("plaintext",$r());R.registerLanguage("python",zr());R.registerLanguage("python-repl",Kr());R.registerLanguage("r",qr());R.registerLanguage("rust",Gr());R.registerLanguage("scss",Hr());R.registerLanguage("shell",Wr());R.registerLanguage("sql",Yr());R.registerLanguage("swift",Zr());R.registerLanguage("yaml",Xr());R.registerLanguage("typescript",Vr());R.registerLanguage("vbnet",Qr());R.registerLanguage("wasm",Jr());R.HighlightJS=R;R.default=R;var jr=R;const na=St(jr);export{na as H}; diff --git a/features/componentlibrary/src/main/resources/resfile/dist/common2.js b/features/componentlibrary/src/main/resources/resfile/dist/common2.js new file mode 100644 index 0000000000000000000000000000000000000000..b285ce54315ce5ef89349a4cf37c92c0edfa6fd5 --- /dev/null +++ b/features/componentlibrary/src/main/resources/resfile/dist/common2.js @@ -0,0 +1 @@ +var o=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function l(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}export{o as c,l as g}; 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..571ddf6b2e68b98a5c196860c1207d82380abb11 --- /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_on_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..88faa14d529543a9a0c64c92da64e4d13c6e578d --- /dev/null +++ b/features/devpractices/src/main/ets/viewmodel/SampleDetailPageVM.ets @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 pageContext: PageContext = AppStorage.get('pageContext') as PageContext; + 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..aa13c20c91a84eeaac05daabc07b0593b1c4ccb9 --- /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": "The HMOS world 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..9b56fb37a24c542d0462c15537471c4520f710ac --- /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": "The HMOS world 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..229cec3753740c2445e8ddcd3685949adcbb0eea --- /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": "HMOS世界支持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..efbee2c3220b68554f5f88e4cb2ad09ef1960c0a --- /dev/null +++ b/features/exploration/src/main/ets/component/ExperienceItem.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 { 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) + .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..c1ec45b0d267b4d10e46a93fd32e34cbdae64ff4 --- /dev/null +++ b/features/mine/src/main/ets/view/AboutView.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 { 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')) + 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..3cca3126c5a4b6bdea5e9087d3ea12f9e9fe2e4e --- /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": "HMOSWorld" + }, + { + "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..70d199582d58f8a5d6fb3ecacf7f8c55b93f14b1 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..3cca3126c5a4b6bdea5e9087d3ea12f9e9fe2e4e --- /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": "HMOSWorld" + }, + { + "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..5e2593e7e737351a1df35e7db912ca826693f1c9 --- /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/hmosword-build/config/mock.json b/hmosword-build/config/mock.json new file mode 100644 index 0000000000000000000000000000000000000000..a7ae1e3ed4b44ba401f96e98c4296d4c0169f2b5 --- /dev/null +++ b/hmosword-build/config/mock.json @@ -0,0 +1,238 @@ +{ + "data": [ + { + "branch": "master", + "originalUrl": "https://gitee.com/zq-kexin/WearableMusic", + "moduleName": "wearablemusicsample", + "abilityName": "WearableMusicSampleAbility" + }, + { + "branch": "master", + "originalUrl": "https://gitee.com/lv-yuanyuan001/SmartWatchShortVideo", + "moduleName": "smartwatchshortvideosample", + "abilityName": "SmartWatchShortVideoSampleAbility" + }, + { + "branch": "master", + "originalUrl": "https://gitee.com/lv-yuanyuan001/SmartWatchMap", + "moduleName": "smartwatchmapsample", + "abilityName": "SmartWatchMapSampleAbility" + }, + { + "branch": "master", + "originalUrl": "https://gitee.com/dong-haifan/SmartWatchCarControl", + "moduleName": "smartwatchcarcontrolsample", + "abilityName": "SmartWatchCarControlSampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/MultiBusinessOffice/", + "moduleName": "multibusinesssample", + "abilityName": "MultibusinesssampleAbility" + }, + { + "branch": "br_release_hmos", + "originalUrl": "https://gitee.com/harmonyos_samples/LiveViewLockScreen/", + "moduleName": "liveviewlockscreensample", + "abilityName": "LiveviewlockscreensampleAbility" + }, + { + "branch": "br_release_hmos", + "originalUrl": "https://gitee.com/harmonyos_samples/knock-share/", + "moduleName": "knocksharesample", + "abilityName": "KnocksharesampleAbility" + }, + { + "branch": "br_release_hmos", + "originalUrl": "https://gitee.com/harmonyos_samples/VideoCast", + "moduleName": "videocastsample", + "abilityName": "VideocastsampleAbility" + }, + { + "branch": "br_release_hmos", + "originalUrl": "https://gitee.com/harmonyos_samples/ContinuePublish/", + "moduleName": "continuepublishsample", + "abilityName": "ContinuepublishsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-nav-bar/", + "moduleName": "multinavbarsample", + "abilityName": "MultinavbarsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-convenient-life/", + "moduleName": "multiconvinientlifesample", + "abilityName": "MulticonvinientlifesampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-mobile-payment/", + "moduleName": "multimobilepaymentsample", + "abilityName": "MultimobilepaymentsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-news-read/", + "moduleName": "multinewsreadsample", + "abilityName": "MultinewsreadsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-travel-accommodation/", + "moduleName": "multitravelsample", + "abilityName": "MultitravelsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-columns/", + "moduleName": "multicolumnssample", + "abilityName": "MulticolumnssampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/text-effects/", + "moduleName": "texteffectssample", + "abilityName": "TexteffectssampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/multi-tab-navigation/", + "moduleName": "multitabnavigationsample", + "abilityName": "MultitabnavigationsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/custom-dialog-gathers/", + "moduleName": "customdialogsample", + "abilityName": "CustomdialogsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/scroll-component-nested-sliding/", + "moduleName": "nestedslidingsample", + "abilityName": "NestedslidingsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/water-flow/", + "moduleName": "waterflowsample", + "abilityName": "WaterflowsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/component-stack/", + "moduleName": "componentstacksample", + "abilityName": "ComponentstacksampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/grid-hybrid/", + "moduleName": "gridhybridsample", + "abilityName": "GridhybridsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/transitions-collection/", + "moduleName": "transitionscollectionsample", + "abilityName": "TransitionscollectionsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/animation-collection/", + "moduleName": "animationcollectionsample", + "abilityName": "AnimationcollectionsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/DragFramework/", + "moduleName": "dragframeworksample", + "abilityName": "DragframeworksampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/fluent-blog/", + "moduleName": "fluentblogsample", + "abilityName": "FluentblogsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/list-exchange/", + "moduleName": "listexchangesample", + "abilityName": "ListexchangesampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/list-item-edit/", + "moduleName": "listitemeditsample", + "abilityName": "ListitemeditsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/window-pip/", + "moduleName": "windowpipsample", + "abilityName": "WindowpipsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/MultipleImage/", + "moduleName": "multipleimagesample", + "abilityName": "MultipleimagesampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/audio-interaction/", + "moduleName": "audiointeractionsample", + "abilityName": "AudiointeractionsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/page-redirection/", + "moduleName": "pageredirectionsample", + "abilityName": "PageredirectionsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/web-pre-render/", + "moduleName": "webprerendersample", + "abilityName": "WebprerendersampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/location-service/", + "moduleName": "locationservicesample", + "abilityName": "LocationservicesampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/preferences/", + "moduleName": "preferencessample", + "abilityName": "PreferencessampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/verification-code-scenario/", + "moduleName": "verificationcodescenariosample", + "abilityName": "VerificationcodescenariosampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/image-comment/", + "moduleName": "imagecommentsample", + "abilityName": "ImagecommentsampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/picker/", + "moduleName": "pickersample", + "abilityName": "PickersampleAbility" + }, + { + "branch": "br_release_hmosworld_new", + "originalUrl": "https://gitee.com/harmonyos_samples/keyboard/", + "moduleName": "keyboardsample", + "abilityName": "KeyboardsampleAbility" + } + ] +} \ No newline at end of file diff --git a/hmosword-build/image/1.png b/hmosword-build/image/1.png new file mode 100644 index 0000000000000000000000000000000000000000..ca122e662774d2bbda1b9619e4eacd175f274d73 Binary files /dev/null and b/hmosword-build/image/1.png differ diff --git a/hmosword-build/image/2.png b/hmosword-build/image/2.png new file mode 100644 index 0000000000000000000000000000000000000000..925becf1e9f2b7c6a13cd995c9da35d70c55cf3b Binary files /dev/null and b/hmosword-build/image/2.png differ diff --git a/hmosword-build/image/3.PNG b/hmosword-build/image/3.PNG new file mode 100644 index 0000000000000000000000000000000000000000..2b464d87688f9788be39f451aa9d69c1191f7efc Binary files /dev/null and b/hmosword-build/image/3.PNG differ diff --git a/hmosword-build/image/4.PNG b/hmosword-build/image/4.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d05227488f1496bf2a840d5ae4ba90daed1b55a1 Binary files /dev/null and b/hmosword-build/image/4.PNG differ diff --git a/hmosword-build/image/5.png b/hmosword-build/image/5.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1791a0feb20516220d422ac1f87d64a1bd92ef Binary files /dev/null and b/hmosword-build/image/5.png differ diff --git a/hmosword-build/image/6.png b/hmosword-build/image/6.png new file mode 100644 index 0000000000000000000000000000000000000000..7de2422f69e89ce24b43f91ee1495a8b0f629590 Binary files /dev/null and b/hmosword-build/image/6.png differ diff --git a/hmosword-build/image/7.PNG b/hmosword-build/image/7.PNG new file mode 100644 index 0000000000000000000000000000000000000000..6dac0aba00575d8fb03d9664cd5d5016f3f3e273 Binary files /dev/null and b/hmosword-build/image/7.PNG differ diff --git a/hmosword-build/image/8.PNG b/hmosword-build/image/8.PNG new file mode 100644 index 0000000000000000000000000000000000000000..0c7addc1f2258c6fc9aec127a14d4fe37c2c4830 Binary files /dev/null and b/hmosword-build/image/8.PNG differ diff --git a/hmosword-build/image/9.png b/hmosword-build/image/9.png new file mode 100644 index 0000000000000000000000000000000000000000..7eec602e8981397b22e99e57477d38445549b2fd Binary files /dev/null and b/hmosword-build/image/9.png differ diff --git a/hmosword-build/image/img_6.png b/hmosword-build/image/img_6.png new file mode 100644 index 0000000000000000000000000000000000000000..cdbc1e8116412fd523e0410a9b9a77b3769b71d0 Binary files /dev/null and b/hmosword-build/image/img_6.png differ diff --git a/hmosword-build/image/img_7.png b/hmosword-build/image/img_7.png new file mode 100644 index 0000000000000000000000000000000000000000..925becf1e9f2b7c6a13cd995c9da35d70c55cf3b Binary files /dev/null and b/hmosword-build/image/img_7.png differ diff --git a/hmosword-build/index.js b/hmosword-build/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d7e435b6de0273cd88877ade12cba6d50ce2efca --- /dev/null +++ b/hmosword-build/index.js @@ -0,0 +1,185 @@ +const fs = require('fs'); +const path = require('path'); +const JSON5 = require('json5'); +const { exec } = require('child_process'); + +const outputDir = path.resolve(__dirname, '../submodules'); +const mockDir = path.resolve(__dirname, './config/mock.json'); + +const excludeSample = ['']; + +// 获取mock数据 +function readMock() { + const samplesMockFile = fs.readFileSync(mockDir, 'utf-8'); + const samplesMockData = JSON.parse(samplesMockFile).data; + console.info('正在下载samples到以下目录...'); + + samplesMockData.forEach((item) => { + loadSample(item); + }) +} + +function loadSample(sampleInfo) { + const { moduleName, originalUrl, abilityName, branch } = sampleInfo; + const sampleName = substringFromStartToEnd(moduleName, 6); + const samplePath = `${outputDir}/${sampleName}`; + console.info(samplePath); + // 排除不需要的sample + if (excludeSample.includes(moduleName) || originalUrl === '') { + return; + } + // 容错处理 + if (fs.existsSync(samplePath) && fs.readdirSync(samplePath).length > 0) { + console.info(`Sample ${sampleName} already exists, skipping...`); + processSample(samplePath, sampleName, moduleName, abilityName); + return; + } else if (fs.existsSync(samplePath) && fs.readdirSync(samplePath).length === 0) { + fs.rmdSync(samplePath, { recursive: true }); + } + exec(`git clone -b ${branch} ${originalUrl} ${samplePath}`, (error, _stdout, stderr) => { + if (error) { + console.error(`${sampleName} failed to download: ${error}`); + return; + } + console.log(`${sampleName} downloaded successfully: ${stderr}`); + processSample(samplePath, sampleName, moduleName, abilityName); + }) + +} + +function processSample(samplePath, sampleName, moduleName, abilityName) { + const sampleBuildPath = `${samplePath}/build-profile.json5`; + let sampleBuildFile; + let sampleBuildData; + + // 修改entry目录名,如果有entry + if (fs.existsSync(path.join(samplePath, 'entry'))) { + fs.renameSync( + path.join(samplePath, 'entry'), + path.join(samplePath, moduleName) + ); + } + + // 修改oh-package5.json + const ohPackagePath = path.join(samplePath, moduleName, 'oh-package.json5'); + if (fs.existsSync(ohPackagePath)) { + let ohPackageData; + try { + ohPackageData = JSON5.parse(fs.readFileSync(ohPackagePath, 'utf-8')); + } catch (error) { + console.error(`Error read oh-package.json5 for ${moduleName}:`, error); + return; + } + ohPackageData.name = moduleName; + fs.writeFileSync(ohPackagePath, JSON5.stringify(ohPackageData, null, 2)); + } else { + console.info(`Sample ${moduleName} has no oh-package.json5, skipping...`); + return; + } + + // 修改module.json5 + const moduleJsonPath = path.join(samplePath, moduleName, 'src/main/module.json5'); + if (fs.existsSync(moduleJsonPath)) { + const moduleJsonData = JSON5.parse(fs.readFileSync(moduleJsonPath, 'utf-8')); + // 修改module.json5的name字段 + const { module } = moduleJsonData; + module.name = moduleName; + module.type = 'feature'; + module.deliveryWithInstall = false; + module.abilities[0].name = abilityName; + module.abilities[0].exported = false; + fs.writeFileSync(moduleJsonPath, JSON5.stringify(moduleJsonData, null, 2)); + } else { + console.info(`Sample ${moduleName} has no module.json5, skipping...`); + } + + if (!fs.existsSync(sampleBuildPath)) { + return; + } + try { + sampleBuildFile = fs.readFileSync(sampleBuildPath, 'utf-8'); + sampleBuildData = JSON5.parse(sampleBuildFile); + } catch (error) { + console.error(`Error parsing build-profile.json5 for ${moduleName}:`, error); + return; + } + // 获取需要的模块 + let cloneSamples = []; + const needModules = sampleBuildData.modules.map(module => { + if (module.name === 'entry') { + cloneSamples.push({ + "name": moduleName, "srcPath": `./submodules/${sampleName}/${moduleName}`, "targets": [{ + "name": "default", + "applyToProducts": [ + "default" + ] + }] + }) + return moduleName; + } else { + cloneSamples.push({ + "name": module.name, "srcPath": `./submodules/${sampleName}/${module.name}` + }) + return module.name; + } + }); + writeBuildProfile(cloneSamples); + // 删除多余目录和文件 + deleteExtraDir(samplePath, needModules); +} + +function deleteExtraDir(samplePath, needModules, moduleName) { + fs.readdir(samplePath, (err, files) => { + if (err) { + console.error('读取目录时出错:', err); + return; + } + files.forEach((file) => { + const filePath = path.join(samplePath, file); + fs.stat(filePath, (err, stats) => { + if (err) { + console.error('获取文件信息时出错:', err); + return; + } + if (stats.isDirectory()) { + if (!needModules.includes(file)) { + fs.rmSync(filePath, { recursive: true }); + } + } else if (stats.isFile()) { + fs.unlinkSync(filePath); + } + }); + }); + }); +} + +function writeBuildProfile(cloneSamples) { + let buildProfileData; + try { + buildProfileData = JSON5.parse(fs.readFileSync(path.resolve(__dirname, '../build-profile.json5'), 'utf-8')); + } catch (error) { + console.error('读取文件build-profile.json5时出错:', error); + } + console.info('写入build-profile.json5配置...'); + const buildModules = buildProfileData.modules.map(item => item.name) + cloneSamples.forEach((sample) => { + if (buildModules.includes(sample.name)) { + return; + } + buildProfileData.modules.push(sample); + }) + fs.writeFileSync( + path.resolve(__dirname, '../build-profile.json5'), + JSON5.stringify(buildProfileData, null, 2) + ); +} + +// 工具函数 +function substringFromStartToEnd(str, n) { + if (typeof str !== 'string' || n < 0 || n > str.length) { + return str; + } + return str.slice(0, -n); +} + +readMock(); \ No newline at end of file diff --git a/hmosword-build/package.json b/hmosword-build/package.json new file mode 100644 index 0000000000000000000000000000000000000000..26ae0570039c2a38709b0bd670ca551d456a1e7d --- /dev/null +++ b/hmosword-build/package.json @@ -0,0 +1,14 @@ +{ + "name": "hmosword-build", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "json5": "^2.2.3" + } +} diff --git a/hmosword-build/screenshots/1.png b/hmosword-build/screenshots/1.png new file mode 100644 index 0000000000000000000000000000000000000000..08615e07dcbf4427ddd85d7dfa412a36cb380eaa Binary files /dev/null and b/hmosword-build/screenshots/1.png differ diff --git a/hmosword-build/screenshots/10.png b/hmosword-build/screenshots/10.png new file mode 100644 index 0000000000000000000000000000000000000000..30dd5e995b8373714baf972d90713f401f436703 Binary files /dev/null and b/hmosword-build/screenshots/10.png differ diff --git a/hmosword-build/screenshots/11.png b/hmosword-build/screenshots/11.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8532a266c94aebd3fc7ab95624e298d51a31cf Binary files /dev/null and b/hmosword-build/screenshots/11.png differ diff --git a/hmosword-build/screenshots/12.png b/hmosword-build/screenshots/12.png new file mode 100644 index 0000000000000000000000000000000000000000..619f3409a1ccde3a00efbeb953f44b2ee879aa9d Binary files /dev/null and b/hmosword-build/screenshots/12.png differ diff --git a/hmosword-build/screenshots/2.png b/hmosword-build/screenshots/2.png new file mode 100644 index 0000000000000000000000000000000000000000..6ec4f8e60baa6a673726e1f48ca451d87c34b6b3 Binary files /dev/null and b/hmosword-build/screenshots/2.png differ diff --git a/hmosword-build/screenshots/3.png b/hmosword-build/screenshots/3.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed3759f0967592083a40225321e86a6eee7e4e7 Binary files /dev/null and b/hmosword-build/screenshots/3.png differ diff --git a/hmosword-build/screenshots/4.png b/hmosword-build/screenshots/4.png new file mode 100644 index 0000000000000000000000000000000000000000..de53034f9d52f829ab059338a48deaddf603fec2 Binary files /dev/null and b/hmosword-build/screenshots/4.png differ diff --git a/hmosword-build/screenshots/5.png b/hmosword-build/screenshots/5.png new file mode 100644 index 0000000000000000000000000000000000000000..473478a3e797d496c8ac06b9cf2bc18be4a3b19e Binary files /dev/null and b/hmosword-build/screenshots/5.png differ diff --git a/hmosword-build/screenshots/6.png b/hmosword-build/screenshots/6.png new file mode 100644 index 0000000000000000000000000000000000000000..30516b25d97e670f8cf0bc6fa5fde65f62e86447 Binary files /dev/null and b/hmosword-build/screenshots/6.png differ diff --git a/hmosword-build/screenshots/7.png b/hmosword-build/screenshots/7.png new file mode 100644 index 0000000000000000000000000000000000000000..fc0bd314ca3467f0e6ba3ea1da7a5f2b5549e840 Binary files /dev/null and b/hmosword-build/screenshots/7.png differ diff --git a/hmosword-build/screenshots/8.png b/hmosword-build/screenshots/8.png new file mode 100644 index 0000000000000000000000000000000000000000..5a87a4b31d83bd4febc51612b22158bdbe188ccf Binary files /dev/null and b/hmosword-build/screenshots/8.png differ diff --git a/hmosword-build/screenshots/9.png b/hmosword-build/screenshots/9.png new file mode 100644 index 0000000000000000000000000000000000000000..6370ea0ff5c211f585ed7bd445977d3094809b2e Binary files /dev/null and b/hmosword-build/screenshots/9.png differ 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..bbcd6677c2b32bea2f695a4b1bd3d320d7e702e9 --- /dev/null +++ b/products/phone/src/main/module.json5 @@ -0,0 +1,102 @@ +{ + "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", + "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..b20e19a14f67a270d6802dee2d78b57cb9da0021 --- /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": "HMOS World" + }, + { + "name": "splash_main_title", + "value": "HarmonyOSWorld" + }, + { + "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": "HMOSWorld" + }, + { + "name": "title_immersive", + "value": "HMOSWorld" + }, + { + "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..4c4671724ec24a20eaad33c37800194a808cbfbb 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..c17d456e4bb7aebc3ad21b4ac87bf8ffdd8eef41 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..36b0832781045a4b2200bb1506d132821b93d442 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..b20e19a14f67a270d6802dee2d78b57cb9da0021 --- /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": "HMOS World" + }, + { + "name": "splash_main_title", + "value": "HarmonyOSWorld" + }, + { + "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": "HMOSWorld" + }, + { + "name": "title_immersive", + "value": "HMOSWorld" + }, + { + "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..22d61518839bd754afa055c2fd5a64736662f4a4 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/resfile/articlecols/articles/app-continuation/ManulImages/1.gif b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/1.gif new file mode 100644 index 0000000000000000000000000000000000000000..bfd612d24b1d9eb023a6a6e33024b003e47bb168 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/1.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/2.png b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..f416baea5c7db05bb901ebbc29ae8ecdf54ef1a1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/3.gif b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/3.gif new file mode 100644 index 0000000000000000000000000000000000000000..f0f9e885e720852a011a28e6edf05f590a4ac0d7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/3.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/4.gif b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/4.gif new file mode 100644 index 0000000000000000000000000000000000000000..3f6ca70fedc44bd74b81b996557622215638fa9c Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/4.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/5.gif b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/5.gif new file mode 100644 index 0000000000000000000000000000000000000000..bd6937fe44f11db9cfd39265aea9f782da037263 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/5.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/6.gif b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/6.gif new file mode 100644 index 0000000000000000000000000000000000000000..3b879aee8ef3340ac4d6de394c768271ce727184 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/ManulImages/6.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/index.html new file mode 100644 index 0000000000000000000000000000000000000000..55444a0a4db4a39d0877a87194b7f87be23f5531 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/app-continuation/index.html @@ -0,0 +1,177 @@ + + + + + + + 跨设备互联,打造无缝流转极致体验 + + + + + +
+

1 跨设备互联,打造无缝流转快速体验

+
+ +
+

1.1 应用接续介绍

+
+

+ 应用接续,指当用户在一个设备上操作某个应用时,可以在另一个设备的同一个应用中快速切换,并无缝衔接上一个设备的应用体验。

+

+ 比如在用户使用过程中,使用情景发生了变化,之前使用的设备不再适合继续当前任务,或者周围有更合适的设备,此时用户可以选择使用新的设备来继续当前的任务。接续完成后,之前设备的应用可退出或保留,用户可以将注意力集中在被拉起的设备上,继续执行任务。 +

+

如图所示,在手机上编辑备忘录,到办公室后切换到平板上继续编辑,完成任务的无缝衔接。

+
图1-1 流转效果
+
+
+
+
+

1.2 应用接续的运行原理

+
+
图1-2 流转运行原理
+

+
    +
  1. 在源端,通过UIAbility的onContinue()回调,开发者可以保存待接续的业务数据。

    例如,浏览器应用完成应用接续,在源端浏览一个页面,到对端继续浏览。系统将自动保存页面状态,如当前页面的浏览进度;开发者需要通过onContinue()接口保存页面url等业务内容。 +

    +
  2. +
  3. 分布式框架提供跨设备应用界面、页面栈以及业务数据的保存和恢复机制,负责将数据从源端发送到对端。
  4. +
  5. 在对端,同一UIAbility通过onCreate()/onNewWant()接口恢复业务数据。
  6. +
+

具体限制参考应用接续约束与规格

+
+
+
+
+

1.3 应用接续的场景化案例

+
+
+
+

1.3.1 典型案例1-内容编辑流转

+
+

+ 在图文编创场景中,使用Ability的自由流转能力,使得编辑内容可以流转到其他更方便的设备上进行接续编辑,这样方便用户在不同设备上进行内容编辑。

+
    +
  1. 用户在A设备编辑页面选择用户照片,并编辑标题、正文等文字信息。
  2. +
  3. 用户点击B设备Dock栏图标,在A设备输入的图片和文字信息衔接到B设备上
  4. +
+
图1-3 内容编辑流转
+

参考案例

+

场景使用:适用于文件类型数据接续,比如图片、视频以及PDF等。

+
    +
  1. 最佳实践《应用接续提升内容发布体验》、《AI辅助图文内容高效编创》。
  2. +
  3. Sample示例代码《应用接续(内容发布)》、《AI辅助图文内容高效编创》。
  4. +
+
+
+
+

1.3.2 典型案例2-列表进度流转

+
+

+ 系统支持用户接续上次浏览位置,无需重新从列表顶部开始滑动查找,精准定位到之前离开的条目附近,节省用户的时间和操作成本,提升在长列表内容浏览时的便利性和体验感。

+
图1-4 列表进度流转
+

参考案例

+

场景使用:适用于List、Grid以及Swiper封装的列表内容流转,比如瀑布流、轮播内容。

+
    +
  1. 最佳实践《浏览进度接续》。
  2. +
  3. Sample代码《浏览进度接续》。
  4. +
+
+
+
+

1.3.3 典型案例3-媒体播放进度流转

+
+

+ 系统支持从源设备暂停的位置无缝续播视频,确保播放进度、画质和音效设置保持一致,为用户提供连贯的观影体验。无论是流媒体平台的影视内容,还是本地存储的视频文件,均能实现流畅的跨设备续播。

+
图1-5 媒体播放流转
+

参考案例

+

场景使用:适用于媒体播放内容的流转,包括音频、视频等。

+
    +
  1. 最佳实践《浏览进度接续》。
  2. +
  3. Sample代码《浏览进度接续》。
  4. +
+
+
+
+

1.3.4 典型案例4-Web浏览进度流转

+
+

系统支持迅速定位到源设备浏览的网页位置,保证用户的浏览连续性,避免重复查找信息的繁琐过程,提高信息获取的效率。 +

+
图1-6 web浏览进度流转
+

参考案例

+

场景使用:适用于Web组件嵌套H5页面内容的流转,主要使用JsBridge桥接。

+
    +
  1. 最佳实践《浏览进度接续》。
  2. +
  3. Sample代码《浏览进度接续》。
  4. +
+
+
+
+

1.3.5 参考资料

+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/1.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/1.gif new file mode 100644 index 0000000000000000000000000000000000000000..1f1848674dae3f61163a5841e0c674a1f8172d62 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/1.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/10.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/10.gif new file mode 100644 index 0000000000000000000000000000000000000000..db32419e8bb2f8e9749206f9e406a8438925147f Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/10.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/11.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/11.gif new file mode 100644 index 0000000000000000000000000000000000000000..4a8ef2f124f8d0d4a95daff6a0d2fc8589321d63 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/11.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/12.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/12.gif new file mode 100644 index 0000000000000000000000000000000000000000..b5f1c9ab0971c54b0f25c45e2236faaf2adfa1b9 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/12.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/13.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/13.gif new file mode 100644 index 0000000000000000000000000000000000000000..ebde945572fdff6198450c0bc1eb9234e5ede1e6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/13.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/14.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/14.gif new file mode 100644 index 0000000000000000000000000000000000000000..9f587d7f27d28535a99aba5ca56f8222ff3f2da8 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/14.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/15.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/15.gif new file mode 100644 index 0000000000000000000000000000000000000000..cf90d67dc205ce4040da8c0c874de19b1d94b3cc Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/15.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/16.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/16.gif new file mode 100644 index 0000000000000000000000000000000000000000..5b993407c888983a47ba8aa711abe1d77245696d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/16.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/17.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/17.gif new file mode 100644 index 0000000000000000000000000000000000000000..271e9a19ec8bcfe236cd136731526e3c07edd0c4 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/17.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/18.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/18.gif new file mode 100644 index 0000000000000000000000000000000000000000..fb6a6addaeb7508a8a52431d42bb46229de9b328 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/18.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/19.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/19.gif new file mode 100644 index 0000000000000000000000000000000000000000..040b3ff93d1c422c55890b01f84e413343575de1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/19.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/2.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/2.gif new file mode 100644 index 0000000000000000000000000000000000000000..6fcb287a84f05e769195bfb72c5fde5c5c3cb874 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/2.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/20.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/20.gif new file mode 100644 index 0000000000000000000000000000000000000000..94fe1f3f6e87514661efc75f943941d9369f4c32 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/20.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/21.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/21.gif new file mode 100644 index 0000000000000000000000000000000000000000..a510bda06c333a6b9f0d3eb556f8dc977009831f Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/21.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/22.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/22.gif new file mode 100644 index 0000000000000000000000000000000000000000..644a6568790f2b6f792db2b21c2caa7f985c100d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/22.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/23.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/23.gif new file mode 100644 index 0000000000000000000000000000000000000000..16ea0946de2f3eaf874d01b29e82a0ea2b80089d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/23.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/24.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/24.gif new file mode 100644 index 0000000000000000000000000000000000000000..55b13f626cb0ba6c311490e9fb6297cf19488f26 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/24.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/25.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/25.gif new file mode 100644 index 0000000000000000000000000000000000000000..399cca2d53d99e8b5a8e122adbebabd24a0122c6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/25.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/26.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/26.gif new file mode 100644 index 0000000000000000000000000000000000000000..20c0ab0fd1154570d767cb8c1eea7ea404b7ebe7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/26.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/27.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/27.gif new file mode 100644 index 0000000000000000000000000000000000000000..d00d8cf8243c9df1457b89d759f599044441d1c6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/27.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/28.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/28.gif new file mode 100644 index 0000000000000000000000000000000000000000..c477ba94c5ac189b1138963acdc2860563db598f Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/28.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/29.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/29.gif new file mode 100644 index 0000000000000000000000000000000000000000..b9b3ce14249942ee0bcc1954ecb8435972b0630d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/29.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/3.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/3.gif new file mode 100644 index 0000000000000000000000000000000000000000..752ebdec6b2718080953f0388346e5d8c0ed53ba Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/3.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/30.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/30.gif new file mode 100644 index 0000000000000000000000000000000000000000..6a212ad0225c63231c2198b3771f7e32d08263a2 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/30.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/31.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/31.gif new file mode 100644 index 0000000000000000000000000000000000000000..c29ac1c85228660546831976efdde4a0ef908945 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/31.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/32.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/32.gif new file mode 100644 index 0000000000000000000000000000000000000000..bb2dfa0339c26e32890e03025553f1e08c32d758 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/32.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/33.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/33.gif new file mode 100644 index 0000000000000000000000000000000000000000..c0c353d68349eaa3726924e88772320552dfafca Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/33.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/4.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/4.gif new file mode 100644 index 0000000000000000000000000000000000000000..9e495f54c64b0e5e416bcdf2fda7469d2f547bef Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/4.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/5.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/5.gif new file mode 100644 index 0000000000000000000000000000000000000000..d9eb5bc5e9c23e05ca0d4c5204fdb3ef8914656d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/5.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/6.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/6.gif new file mode 100644 index 0000000000000000000000000000000000000000..1d32bc0235feedc9b57e3d561b55635bfee4a4b7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/6.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/7.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/7.gif new file mode 100644 index 0000000000000000000000000000000000000000..8520d84b76da248e8898e608b53fdc49b4cbd32d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/7.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/8.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/8.gif new file mode 100644 index 0000000000000000000000000000000000000000..86b251a14579349b859d1b0e95f178a700443ae5 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/8.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/9.gif b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/9.gif new file mode 100644 index 0000000000000000000000000000000000000000..3805d5435580e581ab4f914d1cbe4b84f6a7379c Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/ManulImages/9.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6f3dfef7a38a480a323747f0253d6bfbee49cb6a --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/design-animation/index.html @@ -0,0 +1,128 @@ + + + + + + UX动效设计 + + + + + +

1 UX动效设计

+
+

1.1 UX动效设计

+
图1-1
+

人因x动效

+

力的既视感

+
  • 将力赋予元素
  • 直观地传递出形象化、拟物化、动态化
+
图1-2
+

力的秩序感

+
  • 有序的、一致的、节奏的运动能够创造更强的秩序感
  • 秩序感更容易引起用户注意力
  • 秩序感对转场元素的运动编排尤为重要
+
图1-3
+

力的控制感

+
  • 摩擦力、重力、斥力在交互界面中以操作的方式被感知
  • 力的传递与心理预期产生呼应
  • 元素通过速度或节奏等,传达出仿真物理世界的感受
+
图1-4
+

自然流畅

+
  • 流畅性是人对加工信息难易程度的一种主观体验
  • 三者合一:手势触控、视觉感知、心理预期
  • 基础要求:帧率稳定、响应及时、跟手操作
+
图1-5
+

高效快速

+
  • 界面元素运动尽可能少且简洁
  • 长距离保持在350ms内
  • 短距离保持在250ms内
  • 较短的动画保持在150ms内完成动作
+
图1-6
+

运动一致

+
  • 在动作编排时应遵循运动路径方式一致
  • 不一致的运动会分散用户视觉焦点
  • 运用适当的编排手法,引导用户聚焦操作任务
+
图1-7
+

隐藏等待

+
  • 动效能够减少用户在操作时的等待感
  • 通过元素的运动或转场过渡,减少用户对界面等待产生的焦虑
+
图1-8
+
+
+

1.2 转场动效

+

衔接页面与页面,或元素与元素间的过渡动画称之为转场动效,帮助用户理解界面以及元素之间的逻辑关系。

+

层级转场

+
  • 转场后的页面或元素与当前存在上下层级关系。
+
  • 页面转场采用左右位移的运动方式,不应单帧直接切换或者上下位移切换。
  • 应用场景:上下级页面切换、解锁操作。
+

推荐:列表样式转场采用左右位移的运动方式

+

+

推荐:混合样式切换采用左右位移的运动方式

+

+

不推荐:页面转场采用单帧切换

+

+

搜索转场

+
  • 一镜到底是通过共享元素、容器和动势来进行转场过渡的一种编排方式。
  • 搜索框作为持续存在的元素前后串连,建议使用一镜到底;不应单帧直接切换或者非共享元素的方式转场。
  • 曲线优先使用弹簧曲线。
+

推荐:一镜到底

+

+

推荐:淡入淡出

+

+

不推荐:左右平移

+

+

新建转场

+
  • 新建功能目的性层、级性明确。
  • 系统页面转场采用上下位移的半模态运动方式,不应单帧直接切换或者左右位移切换。
  • 曲线优先使用弹簧曲线。
+

推荐:半模态上下位移

+

+

推荐:淡入淡出

+

+

不推荐:左右平移

+

+

编辑转场

+
  • 建议使用淡入淡出的过渡方式。
  • 不应单帧直接切换或者位移切换。
  • 曲线优先使用弹簧曲线。
+

推荐:一镜到底

+

+

不推荐:左右切换

+

+

不推荐:上下平移

+

+

卡片打开

+
  • 前后相邻界面切换跳转,或较大元素的进出场必须有转场动效过渡,如无特殊原因。
  • 推荐使用一镜到底的共享元素手法。
  • 不应单帧直接切换(含动效设计缺失和实现效果丢失两种情况)。
  • 全屏页面的转场动效时长,8.5英寸以下设备不应短于200ms。
  • 8.5至12英寸的设备上不应短于250ms。
  • 12英寸以上的设备上不应短于300ms。
+

推荐:一镜到底

+

+

推荐:一镜到底

+

+

不推荐:单帧切换

+

+

推荐:一镜到底

+

+

不推荐:单帧切换

+

+

多种编排

+
  • 同类型动效编排方式差异:例如手机上使用图标到界面共享容器的方式;智慧屏上采用共享动势的方式来避免过大的位移路径给用户带来不适感。
  • 应用启动时(包括通过图标、服务卡片、通知卡片等入口)应尽量避免显示无内容的启动页(全黑、全白色彩填充)。
  • 有内容填充的启动页在全屏状态停留时长不建议超过3s,全黑、全白的空白无内容启动页在全屏状态停留时长不建议超过300ms。
+

推荐:一镜到底

+

+
+
+

1.3 手势动效

+

手在终端屏幕或触摸板等输入设备上进行跟手操作的动效,大致分为点击、滑动、拖拽等手势。

+

列表滑动

+
  • 通过弹性曲线等物理模型,带来流畅的操作反馈感,还原真实可行的手势操作体验。
  • 可滑动页面达到一定手速的滑动操作时,手离开屏幕后,界面应继续移动。
  • 移动速度应该随时间缓慢下降,直至界面停止。
  • 上下滑动到顶部或底部,应该有反馈动效。
  • 左右滑动到左边界或右边界),应该有反馈动效。
+

推荐:不满一屏滑动

+

+

不推荐:页面不跟手

+

+

推荐:过界回弹

+

+

不推荐:无阻尼&过界无回弹

+

+

推荐:大小标题联动

+

+

翻页滑动

+
  • 翻动通过抛滑和拖拽手势进行操作,由翻页这类场景承载。
  • 翻页是基于设定区域进行对象内容的翻动切换。
  • 到达边界后应尽可能赋予回弹的操作体验。
+

推荐:翻页跟手

+

+

推荐:过界回弹

+

+
+
+
+ + + + diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..aa5994804b0d4096af88509afc8f620b923b97b2 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/2.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..7d4f9815f89d55f4a35f3e8ee72e2e4b541c6bbc Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/3.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/3.png new file mode 100644 index 0000000000000000000000000000000000000000..2d02eee7d337ce8a970de13a551c41a93b19854d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/4.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..aa53c1880b4dbb4792299974dc7a5dfd0d9f649d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/4.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/5.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/5.png new file mode 100644 index 0000000000000000000000000000000000000000..4a630b224dc8c114a01b7052ae7417efdc3b0f19 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/5.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/6.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/6.png new file mode 100644 index 0000000000000000000000000000000000000000..6cf741cfa76db32dfd9a357193cdd59a4c1ba687 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/6.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/7.png b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/7.png new file mode 100644 index 0000000000000000000000000000000000000000..6a59f8c273969536c9ce92b3ebc00788d1290762 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/ManulImages/7.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/index.html new file mode 100644 index 0000000000000000000000000000000000000000..cb3850057b8fcfc6487e7310ed29e98d5caaa6c2 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/layered-architecture-design/index.html @@ -0,0 +1,393 @@ + + + + + + + 分层设计最佳实践 + + + + + +
+

1 分层设计最佳实践

+
+ +
+

1.1 概述

+
+

+ HMOS世界的分层架构设计是为了在同一套代码工程的基础上,构建手机、平板等1+8设备上的产物,实现HarmonyOS应用的“一次开发,多端部署”的理念。

+

本文将从HMOS世界应用的设计、开发、部署三个视角说明HarmonyOS应用的三层架构规则与实践。

+
+
+
+
+

1.2 设计-逻辑模型

+
+
+
+

1.2.1 设计原则

+
+

+ 典型的鸿蒙应用分层架构主要包括产品定制层、基础特性层、公共能力层,通过这样分层设计构建出一个清晰、高效、可扩展的设计架构。应用三层架构如下图:

+

+
    +
  • + Products层(产品定制层):包含不同设备形态的个性化业务,比如UI、资源和配置。与应用底座解耦,各个Product之间不可以横向依赖,向下依赖Features层和Common层。
  • +
  • + Features层(基础特性层):指抽象应用基础特性集合。每个Feature高内聚,低耦合,可定制,供产品灵活部署。可以被Products层不同设备形态的Hap所依赖,但是不能反向依赖Products层,Features可以向下依赖Common层。 +
  • +
  • + Common层(公共能力层):指基础能力集,是最小系统集。各个Feature包含的公共业务,都可以下沉到Common层。该层只可以被Products层和Feature层依赖,不可以反向依赖。
  • +
+

+ 以上分层设计是将代码按照功能职责进行划分,每一层专注负责该层职责,提升代码的可维护性、可拓展性和代码复用性。分层设计优势如下:

+
    +
  • 解耦:不同模块之间尽量减少依赖,降低耦合度。
  • +
+
    +
  • 复用:公共能力可以被多个模块复用,避免重复开发。
  • +
  • 清晰性:通过明确的层次划分,便于快速理解项目结构。
  • +
  • 扩展性:新增功能时,只需在对应层添加模块,而不会影响其他部分。
  • +
+

同时对比传统的移动应用架构,鸿蒙应用三层架构在以下几个方面具有优势:

+
    +
  • 代码工程

    传统架构:对于一个应用,在多设备场景下,可能存在多套代码工程,如手机、平板、手表每种设备使用不同工程 ,开发者维护成本高。

    +

    三层架构:对于一个应用,可以实现一套代码支撑不同设备类型,开发者维护成本低。

    +
  • +
  • 代码复用度

    传统架构:对于一个应用,在多设备场景下,代码复用度低

    +

    + 三层架构:相比传统架构,特性层是产品之间公共能力的复用,公共层是特性层之间公共能力的复用。相同的业务处理逻辑可以在不同设备上复用。整体代码复用度高。

    +
  • +
  • 应用包部署

    传统架构:对于不同的设备类型,开发者通常需要构建出对应设备类型的部署包,分别上架对应应用市场。

    +

    三层架构:对于不同的设备类型,开发者统一打包app包上架应用市场。应用市场根据运行时设备类型进行分发。

    +
  • +
+
+
+
+

1.2.2 HMOS世界案例

+
+

+ HMOS世界在业务上需要支持手机、折叠屏、平板、PC这些设备,结合以上典型鸿蒙应用的分层架构设计,HMOS世界客户端的分层设计如下:

+

+

Products层

+

+ 该层的主要负责提供应用在不同设备上的个性化业务,包含设备差异化的UI、资源和配置文件。当前HMOS世界支持手机、平板设备类型,后期将支持PC端,因此在产品定制层划分出手机、平板、PC三个不同设备入口。

+

Features层

+

+ 该层是由应用的不同业务特性聚合而成,应用需根据自身业务划分出各个基础特性。结合HMOS世界当前运行实际业务,可划分为以下几个特性:

+
    +
  • 组件体验业务:负责提供ArkUI组件体验、系统能力体验、关键代码预览、相关资料、相关海报内容查阅等能力。 +
  • +
  • + 开发实践业务:负责提供Sample预览介绍、Sample按需加载体验、Sample源码查阅、相关海报内容查阅等能力。
  • +
  • 发现业务:负责提供技术文章查阅、开发资讯查阅、相关海报内容查阅等能力。
  • +
  • 我的业务:负责提供华为账号登陆管理、用户声音收集、版本更新、用户收藏内容管理。
  • +
  • 桌面卡片业务:负责给用户提供桌面卡片。
  • +
+

Common层

+

+ 该层是应用各个特性的基础能力集,由各特性的公共业务组成。结合HMOS世界特性模块划分,其公共能力包括公共UI组件、海报预览模块、文章预览模块、视频播控模块、特性包管理模块、存储服务、路由模块、桌面卡片服务、华为账号管理服务、消息推送模块、运维模块、版本管理等。 +

+
+
+
+
+

1.3 开发-代码与构建模型

+
+
+
+

1.3.1 设计原则

+
+

+ 根据本文上述的逻辑模型提出的三层架构划分,以支持手机、PC产品为例,其对应典型的代码和编译模型如下图所示:

+

+

Products层

+

针对不同设备形态(DeviceType),编译出不同的类型的 Hap包。

+
+ +
+

上述的构建模型是应用针对不同的设备形态使用不同的Entry Hap包,称之为Entry分包方案。

+

同样,应用可以在不同设备上使用一个共同的Entry + HAP包,即entry共包方案。例如,Product层编译出一个Default.hap,直接分发到手机、平板、PC设备上。

+

分包、共包的设计选型,将在本文的应用部署原则详细展开。

+
+
+
Features层 +
    +
  1. 不需要单独部署的Feature,编译成HAR包,打包到Product层的Entry Hap中。
  2. +
  3. + 需要单独部署的Feature,编译成Feature类型的Hap包。然后再Product层的Hap包,组成Entry Hap包 +Feature Hap包的方式组合部署。
  4. +
  5. 每一个Feature要做1+8设备的自适应设计。
  6. +
+
+

Common层

+

该层不可被分割,可以根据实际业务需求,编译成Har包或者Hsp包,打包到 Product 层的 Entry + Hap中。

+

最后,整个代码工程最终构建出一个APP包,应用以APP包的形式发布到应用二进制流水线或者华为应用市场中。

+
+
+
+

1.3.2 HMOS世界案例

+
+

基于以上典型鸿蒙应用的代码与构建模型设计原则,HMOS世界的代码工程模型和编译构建产物整体过程如下图:

+

+

代码结构

+

HMOS世界自身代码结构根据逻辑模型的三层划分,分别创建以下目录和相关子目录,以组织相关业务代码。

+
├─common                  // 公共能力模块
+│  ├─accountservice       // 账号服务
+│  ├─pushservice          // 推送服务
+│  ├─routermanager        // 路由管理
+│  ├─storagemanage        // 本地存储管理
+│  ├─trackmanager         // 埋点管理
+│  ├─...                  // 其余模块
+├─features                // 基础特性层
+│  ├─commonbusiness       // 业务公共能力模块   
+│  ├─componentlibrary     // 体验模块
+│  ├─devpractices         // 实践模块 
+│  ├─exploration          // 发现模块 
+│  └─mine                 // 我的 
+├─products                // 产品定制层
+│  ├─phone                // 手机、Pad产品
+│  ├─pc                   // pc产品
+└─samples                 // Samples集成 
+      └─xxxsample           // 集成到HMOS世界内的Sample
+

Products层编译

+

+ HMOS世界在业务上需同时支持手机、平板、PC三个设备端,结合下文的应用部署原则中的应用分包/共包设计原则,考虑到手机、Pad部署特性相同,仅UX页面差异,决定手机和Pad采用共包方案,页面差异通过系统的页面级一多能力实现;PC业务后期会部署PC独有特性,因此PC端采用分包方案,单独编译PC产品的Hap包。 +

+
Features层编译 +
    +
  1. + HMOS世界自身业务不需要单独部署的特性,编译成Har包,打包到Product层的Entry的Hap包内。
  2. +
  3. HMOS世界上架的Sample代码,需要单独编译成Feature + Hap包。以Product层的Hap包+Sample Hap包组合部署。
  4. +
  5. 各个上架HMOS世界的Sample需要单独做多设备适配。
  6. +
+
+

Common层编译

+

基础能力各个模块,编译成各个独立的Har和Hsp包,并且打包到Product层的Entry Hap中。

+
+ +
+

Common层选择Har和Hsp打包类型可参考如下规则:

+

1. + 对于单Hap场景,如果没有按需加载的业务诉求,直接采用Hap+Har方式;反之当有按需加载诉求时,可以考虑将按需加载模块打包为Hsp,并且当按需加载的模块与Hap之间有公用资产时,可以进一步考虑将公共资产也打包成Hsp。 +

+

2. + 对于多Hap场景,如果多Hap之间没有公用的资产,直接采用Hap+Har方式;反之多Hap之间有公共资产时,可以考虑将公共资产打包成Hsp。

+

3. Hsp包在运行时需动态加载执行,可能会导致性能问题。

+

4. 多包(Hap/Hsp)引用相同的Har时,会造成多宝间的代码和资源重复拷贝,导致应用包体积过大。

+
+
+

集成打包

+

整个代码工程最终构建出一个App包,应用以App包发布。

+
+
+
+
+

1.4 部署-应用部署模型

+
+
+
+

1.4.1 设计原则

+
+

分包部署

+

分包部署是指在不同的设备上使用不同的entry HAP包,如下图示意:

+

+

共包部署

+

共包部署是指在不同的设备上使用同一个的entry + HAP包,如下图示意了一个Default.hap同时部署在手机与平板端。

+

+

应用分包/共包设计原则

+

+ 应用在不同设备上使用分包还是共包,取决于应用在不同设备的特性差异,特性差异包括布局界面和功能性上的差异两部分,差异小的设备间建议采用共包方案,差异大(分包后 ROM + 收益大)的设备间可以采用分包方案。主要的参考原则如下:

+
    +
  1. 有桌面图标类的应用,建议平板和 PC 两个设备上使用 entry 共包的方案;
  2. +
  3. 不同设备间存在差异化 Feature,差异化 Feature 所占 ROM 空间较大,建议分包; +
  4. +
  5. 不同设备上同一断点的布局显示结构差异很大时建议分包,反之则共包;

    原则2优先于原则3。

    +
  6. +
+

举例 1:以车机的桌面和平板的桌面为例,同样是 lg 断点,但其布局差异非常大,建议分包。

+

举例 2:笔记 App,手机上的 sm、md 断点与平板 PC 上的 sm、md 断点布局显示基本一致,建议共包。 +

+

下面是不同的设备上需支持的断点列表:

+ +
表1-1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

断点

+
+

手机

+
+

平板

+
+

PC

+
+

sm断点

+
+

需要支持

+
+

需要支持

+
+

需要支持

+
+

md断点

+
+

需要支持(折叠屏)

+
+

需要支持

+
+

需要支持

+
+

lg断点

+
+

建议支持(后续分布式场景拓展)

+
+

需要支持

+
+

需要支持

+
+
+

部署按需加载特性包

+

按需加载定义按需加载是指用户首次从应用市场安装时,仅会下载应用的基础的Hap包进行安装,应用运行时使用到相关功能时,再动态下载安装运行对应的功能模块。 +

+

按需加载流程参考官网

+

按需加载优势

+
    +
  1. + 减少包体积:用户从应用市场首次下载的应用不包含按需加载模块,用户看到的包体积减少,从而减少了用户下载和安装时间,减少了用户等待时间。
  2. +
  3. + 减少系统资源:应用安装之后所占用的空间也变少(节省ROM空间),应用启动时加载的特性少了(节省了RAM空间)。
  4. +
  5. 架构演进:将特性定义为按需加载之后,对特性的定义和模块间的耦合关系进一步明确,有利于应用架构进一步演进。 +
  6. +
+
+
+
+

1.4.2 HMOS世界案例

+
+

+ HMOS世界在业务上需同时支持手机、平板、PC三个设备端,考虑到后期在手机平板页面布局有差异,且存在差异化特性。因此在不同设备使用不同的entry hap包,entry进行分包。

+

HMOS世界内集成的Sample示例代码工程在上述构建过程中被编译成Feature + Hap,运行时根据需要通过StoreKit按需下载并拉起运行,为部署方式为按需加载。此外,Sample工程编译的Feature Hap需要适配多设备。

+

鸿蒙世界的客户端在设备部署如下图:

+

+
+
+
+
+

1.5 总结

+
+

+ 本文从应用开发的设计、开发、部署全流程,围绕三层架构设计,讲述了鸿蒙应用开发的逻辑模型、代码和构建模型、应用部署模型设计原则和其在HMOS世界中的实际应用。展示了一个清晰、高效、可拓展的设计模型,实现HarmonyOS应用的一次开发多端部署理念。 +

+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..d574b7b9eb0bdc5e2cc97969065f70cb6311608c Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/2.png b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..f76058ad826d22b540137433c91f5cdfbee6061d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/3.png b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/3.png new file mode 100644 index 0000000000000000000000000000000000000000..8ae3ac32aaaff992345fe15959dd39f296d5e96c Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/4.png b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..b304548a41c2c2da74c4f6b74f3c89cf8cccb6da Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/4.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/5.png b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/5.png new file mode 100644 index 0000000000000000000000000000000000000000..cb053d953e0cf999a0aca222776cbec38bb73824 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/5.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/6.png b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/6.png new file mode 100644 index 0000000000000000000000000000000000000000..4e0b723c2abe16438e20c63c37983f8ce4c25e5e Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/ManulImages/6.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/index.html new file mode 100644 index 0000000000000000000000000000000000000000..126d15909f27970f568e37ad16a1259277cdcc36 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/live-window-service/index.html @@ -0,0 +1,287 @@ + + + + + + + 小窗口大世界,智享实况窗服务 + + + + + +
+

1 小窗口大世界,智享实况窗服务

+
+ +
+

1.1 实时掌控重要信息变化

+
+

+ 移动互联网时代,各种以用户为中心的App如春笋般涌现,满足了用户在购物、导航、娱乐等不同场景下的需求,不同程度上丰富了用户的日常生活。然而,随着App种类的激增,用户在享受多样化服务的同时,也面临着多任务管理的挑战。 +

+

+ 比如,用户正沉浸在游戏或追剧中时,能否想起并打开App查看一下:不久前点的外卖是否已上路?网约车是否已到达?餐厅预订的餐品是否已准备好? + 在多应用进程并行使用的情况下,这些重要的服务很容易被忽略,从而影响到用户的日常生活质量与效率。

+
图1-1 通知中心和锁屏
+

+ 这时候,就可以使用实况窗服务。该服务支持应用将订单或服务的实时状态信息变化在设备的熄屏、锁屏、通知中心、状态栏等关键界面展示,并对展示信息的生命周期、用户界面UI效果等进行管理,在特定的情况下还可以震动或发出特定声音来提醒用户,帮助用户聚焦正在进行的任务,方便查看和即时处理通知内容。 +

+
图1-2 状态栏
+
+
+
+
+

1.2 丰富的场景支持 +

+
+

实况窗场景适用于出行打车、高铁、送餐等场景,具体可支持对接的场景如下表所示,更详细的场景说明请参考这里

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

场景类型

+
+

适用范围

+
+

适用范围

+
+

出行打车

+
+

用户线上约车后,向用户展示司机接驾等待时间、行程中的剩余距离和时间等信息。

+
+

适用于网约车、出租车、拼车、顺风车等场景。

+
+

即时配送

+
+

指配送员将餐品、商品送达到用户指定地点的业务场景,通常在较短时间内完成配送环节。

+
+

适用于外卖、生鲜配送、同城配送等场景。

+
+

航班

+
+

+ 用户主动关注某个航班时,向用户展示航班的关键变动,如航班开始登机、航班起飞、航班延误、航班取消、航班到达等关键场景。

+
+

适用于用户通过航班出行或者主动关注某个航班进展的场景。

+
+

高铁/火车

+
+

用户通过高铁、火车出行,向用户展示检票口、座位号、车次信息及列车运行状态等信息。

+
+

适用于高铁出行、火车出行的场景。

+
+

排队

+
+

需要通过排队叫号的方式,按顺序为用户提供服务的业务场景。

+
+

适用于办事大厅、医院、银行、餐饮等排队叫号能力场景。

+
+

取餐

+
+

指的是用户完成餐品/商品下单后,自行取餐或者取件的场景。

+
+

适用于餐饮线下取餐提醒,包括餐品排队情况、制作进度、取餐提醒等。

+
+

赛事比分

+
+

展示比赛双方成绩变化情况。

+
+

适用于游戏赛事、体育赛事等展示比分变化情况的场景。

+
+

共享租赁

+
+

用户使用临时租赁服务时,向用户展示实时租赁时长和费用等租赁状态信息的场景。

+
+

适用于共享单车、共享充电宝、停车场临时停车等场景。

+
+

计时

+
+

用户在某个短时间段持续的正计时或任务前的倒计时场景。

+
+

适用于专注时刻、番茄时钟、抢票倒计时提醒场景,仅限于工具类应用申请(计时场景仅支持通过端侧创建与更新)。 +

+
+

运动锻炼

+
+

运动过程中,向用户实时展示运动的时长和进度等信息。

+
+

适用于户外或室内的运动记录,如跑步、骑行等。

+
+

导航

+
+

用户使用导航服务时,展示将要发生的路线变化。

+
+

适用于步行导航、骑行导航、车辆导航。

+
+
+
+
+
+
+

1.3 热门场景案例

+
+

+ 在设计特定应用场景的实况窗时,开发者需要考虑应用服务进程中需要设置提醒的关键节点与呈现的内容信息,这也是用户在使用实况窗过程中最关注的部分,在这里我们用三个热门场景来举例说明。

+
+
+
+

1.3.1 即时配送场景

+
+

+ 即时配送的关键节点可分为用户下单、等待用户支付、等待商家接单、商家已接单、骑手取货中、骑手取货中、骑手配送中、商品已送达、放入取餐柜,用户可以通过实况窗实时得知外卖的配送进度,而无需频繁点开应用详情页查看。

+
图1-3 即时配送节点图
+

详细场景节点设计可以参考即时配送

+
+
+
+

1.3.2 航班场景

+
+

+ 航班场景的关键节点分为未值机、已值机、开始登机、催促登机、已登机、已起飞、已抵达、延误、取消、登机口变更,实况窗在各个节点会显示不同的内容,同时提醒用户作出对应的响应

+
图1-4 航班节点图
+

延误取消登机口变更等特殊情况节点:

+
图1-5 特殊情况节点图
+

更加详细的航班场景节点设计可以参考航班场景

+
+
+
+

1.3.3 打车出行场景

+
+

打车出行按照用户是否立即出发,分为即时出行场景和预约出行场景。

+

+ 场景节点主要分为叫车、排队、司机正在完成上一单、司机本单接驾中、即将到达上车点、司机已到达、开始前往目的地、到达目的地。

+

用户可以在任意节点了解当前车辆和行程信息。

+

预约出行场景,需计划出发前20分钟开始展示实况窗提醒用户。

+
图1-6 打车出行节点图
+

更加详细的节点设计可以参考打车出行场景

+
+ +
+

以上三种场景均有相关Sample代码:代码地址,实况窗更加丰富内容可以参考实况窗服务文章。

+
+
+

+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..1fd91c78d9cdafd6b89b2a72bd298010662498ff Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/10.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/10.png new file mode 100644 index 0000000000000000000000000000000000000000..e51cce9c24e923e54c8710c27132f831ad780387 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/10.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/11.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/11.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f29172531452fd30dcf0aba8e3c6545c6f122d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/11.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/12.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/12.png new file mode 100644 index 0000000000000000000000000000000000000000..73901d8bc53a8f78625250c4012e0a771e49a5af Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/12.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/13.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/13.png new file mode 100644 index 0000000000000000000000000000000000000000..a992eabfe5a2cc45fbaaa7264f51a4911af8b527 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/13.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/14.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/14.png new file mode 100644 index 0000000000000000000000000000000000000000..dbb167209108da75bfe8830899f060b523f5d2aa Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/14.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/15.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/15.png new file mode 100644 index 0000000000000000000000000000000000000000..8359a6fa8085a439d3ff7b2c4a92bff7caf24119 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/15.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/2.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..0358d990ffb35f202be0503fcceb58c3329fe170 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/3.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/3.png new file mode 100644 index 0000000000000000000000000000000000000000..1d406cb453dee59b46dc43432ce10e1c8a6267e3 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/4.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..bf394abd2c66780add7a67888047f5167d3e9b4f Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/4.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/5.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/5.png new file mode 100644 index 0000000000000000000000000000000000000000..9e072f3d898f77997656c26f9e66744ee474bbd5 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/5.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/6.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/6.png new file mode 100644 index 0000000000000000000000000000000000000000..3e4c5a46881cd113917b946a4accc09f5cd1c4a1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/6.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/7.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/7.png new file mode 100644 index 0000000000000000000000000000000000000000..4518ec1277fedec9c37025ade021cfb0d96eee62 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/7.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/8.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/8.png new file mode 100644 index 0000000000000000000000000000000000000000..522e953fbb7f4b2509c510452896b56a3a1e8846 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/8.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/9.png b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/9.png new file mode 100644 index 0000000000000000000000000000000000000000..5a853053cb5e0faa73aa63e2e1b50694badd14ec Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/ManulImages/9.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/index.html new file mode 100644 index 0000000000000000000000000000000000000000..57eefb9ed1eaa48388678443c3697433b53a766e --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/multi-adaptation/index.html @@ -0,0 +1,588 @@ + + + + + + + 一多适配 + + + + + + +
+

1 一多适配

+
+

+ HMOS世界是一个基于开发者技术演进的大型代码工程最佳实践;该应用以开发者技术地图为根,体现HarmonyOS家族式的UX设计,用来展示HarmonyOS亮点特性、关键体验特征、生态创新场景、高频开发场景等;本应用除了能在手机端正常运行,也通过“一次开发,多端部署”的原则,实现了Pad和PC/2in1的适配。下边简要介绍下在开发过程中一多的适配过程。 +

+
+ +
+

1.1 一多应用开发流程

+
+

一多应用开发的整体过程大致分为:UX设计->工程管理->页面开发。

+
    +
  • + UX设计:一多的应用UX设计需遵循通用设计规则,应该考虑多设备的“差异性”、“一致性”、“灵活性”和“兼容性,以此来保障各设备上应用界面的体验,在开发过程中最大程度复用界面代码。详细规范请参见应用UX设计原则
  • +
  • 工程管理:“一多”推荐在应用开发过程中使用“三层工程结构”,以便在不同设备之间更好的实现代码复用,减少不同模块之间不必要的依赖。
  • +
  • 页面开发:页面开发主要通过自适应布局响应式布局能力来实现同一页面在不同设备上的不同显示效果,同时建议开发者多使用自定义组件,这样在增加代码可读性和可维护性的同时也可以尽可能的实现代码复用。 +
  • +
+

下面从UX设计、工程管理和页面开发三个方面来介绍HMOS世界应用的一多开发过程。

+
+
+
+
+

1.2 HMOS世界一多适配过程

+
+
+
+

1.2.1 UX设计

+
+

HMOS世界UX设计相关内容,参考UX设计技术文章 +

+
+
+
+

1.2.2 工程管理

+
+

HMOS世界根据一多推荐的common(公共能力层)、feature(基础特性层)、product(产品定制层)的“分层架构设计规则”划分目录。

+
├─common                  // 公共能力模块
+├─features                // 基础特性层
+│  ├─commonbusiness       // 业务公共能力模块   
+│  ├─componentlibrary     // 组件模块
+│  ├─devpractices         // 样例模块 
+│  ├─exploration          // 事件模块 
+│  └─mine                 // 我的 
+└─products                // 产品定制层 
+   └─phone
+
+
+
+

1.2.3 页面开发

+
+
+

1.2.3.1 断点使用

+
+

断点以应用窗口的宽度为切入点,将应用窗口在宽度维度上分为不同的区间即不同的断点,在不同的区间下,开发者可以根据需要实现不同的页面布局效果,解决多设备的UX布局问题。断点功能又分为横向断点和纵向断点,使用断点来适配一多时开发者无需关注设备类型,即可达到多设备的自适应UI布局效果。纵向和横向断点的详细信息请参考多设备断点开发实践

+
+
+
+

1.2.3.2 组件列表页

+
+

+ 组件列表页面作为app的入口页面,主要负责展示组件卡片,给用户提供不同组件的展示入口。主要包含页签区域和内容区域两部分,页签区域在手机和折叠屏展开态时会在页面底部,在Pad上会在页面左侧;内容区域会根据设备大小不同展示不同列数,详细样式见下表。 +

+ +
+ + + + + + + + + + + + + + + +
+

sm

+
+

md

+
+

lg

+
+

+
+

+
+

+
+
+

页签区域适配

+

+ 页面整体在手机上属于上下布局,上边是内容区域,底部是页签,在折叠屏展开态和pad上页签和内容区域属于左右布局,因此页面整体使用Tabs组件,然后根据不同的断点值来改变tabBar的位置

+
// products/phone/src/main/ets/page/MainPage.ets
+Tabs({ controller: this.tabController, index: this.currentIndex }) {
+  TabContent() {
+    ComponentListView()
+  }
+  .tabBar(this.TabItemBuilder(TABS_LIST[TabBarType.HOME]))
+  // ...     
+}
+.vertical(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG) 
+.barPosition(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? BarPosition.Start :
+BarPosition.End)
+.barHeight(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? '50%' :
+  this.globalInfoModel.naviIndicatorHeight + CommonConstants.TAB_BAR_HEIGHT)
+.barOverlap(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.LG ? false : true)
+

内容区域适配

+

+ 组件列表页面内容区域在手机上是列表样式,在折叠屏展开态和pad上是顶部通栏banner,底部是通栏item,中间区域是瀑布流,因此在布局上选择的是WaterFlow容器,使用瀑布流分组WaterFlowSections实现。顶部banner组和底部item组默认FlowItem数量、列数均为1,中间瀑布流组的列数根据不同的断点分别设置为1,2,3,在组件卡片数据请求回来之后,设置瀑布流组的FlowItem数量为总卡片数量。 +

+

代码实现如下:

+
// features/componentlibrary/src/main/ets/viewmodel/ComponentListViewModel.ets
+private componentColumnSection: SectionOptions = {
+  itemsCount: 0,
+  crossCount: new BreakpointType({
+    sm: 1,
+    md: 2,
+    lg: 3
+  }).getValue(this.globalInfoModel.currentBreakpoint),
+  margin: $r('sys.float.padding_level8'),
+ }
+// ...
+protected updateFlowSection() {
+  this.componentColumnSection.itemsCount = this.state.cardData.length;
+  this.componentColumnSection.crossCount = crossCount;
+  this.state.sections.splice(0, this.state.sections.length(),
+    [this.bannerColumnSection, this.componentColumnSection, this.footerSection]);
+}
+

+ 将上述代码中设置好的componentColumnSection数据赋值给WaterFlow组件的sections参数,即可实现瀑布流的分组显示。

+
// features/componentlibrary/src/main/ets/view/ComponentListView.ets
+WaterFlow({
+    scroller: this.scroller,
+    sections: this.componentListState.sections
+  }) {
+    FlowItem() {
+      BannerCard({
+       // ...
+      })
+    }
+    Repeat(this.componentListState.cardData)
+      // ...
+    FlowItem() {
+      LoadingMoreItemBuilder(this.componentListState.loadingModel)
+    }   
+  }
+   .rowsGap(new BreakpointType({
+    sm: $r('sys.float.padding_level8'),
+    md: $r('sys.float.padding_level6'),
+    lg: $r('sys.float.padding_level6')
+  }).getValue(this.globalInfoModel.currentBreakpoint))
+   // ...
+}
+
+
+
+

1.2.3.3 样例页

+
+

+ 样例页面以多种形态的卡片来展示sample信息,单sample有大图卡片和上图下文两种卡片样式,sample合集有列表卡片和通栏横向滑动样式效果,具体UX效果见下表。

+ +
+ + + + + + + + + + + + + + + +
+

sm

+
+

md

+
+

lg

+
+

+
+

+
+

+
+
+

+ 页面在手机上以列表形式展示,在pad上存在多种不同的样式,每个卡片的高度一致,宽度占比有1/3,2/3,3/3等多种效果,结合一多的适配规则,选择响应式布局GridRow组件来解决不同的卡片在不同设备上的动态布局问题。

+

在不同尺寸的设备上,按照断点分别将栅格的布局列数设置为4, 8, 12, + 然后根据sample卡片的类型以及其在不同设备上的宽度占比,分别设置其所在GridCol占用栅格容器组件的列数。

+

代码实现如下:

+
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)
+    .templateId((item: SampleCardData) => item.cardStyleType.toString())
+    .template(CardStyleTypeEnum.PICTURE_ABOVE_TEXT.toString(),
+      (repeatItem: RepeatItem<SampleCardData>) => {
+        GridCol({ span: CommonConstants.SPAN_4 }) {
+          // ...
+        }
+      })
+    .template(CardStyleTypeEnum.PICTURE.toString(), (repeatItem: RepeatItem<SampleCardData>) => {
+      GridCol({ span: { sm: CommonConstants.SPAN_4, md: CommonConstants.SPAN_4, lg: CommonConstants.SPAN_8 } }) {
+        // ...
+      }
+    })
+    // ...
+}
+
+
+
+

1.2.3.4 实践页

+
+

实践页以分组的形式来展示各个分类下的文章列表,每个分类下的文章卡片样式各不相同,UX效果见下表

+ +
+ + + + + + + + + + + + + + + +
+

sm

+
+

md

+
+

lg

+
+

+
+

+
+

+
+
+

+ 由UX效果可以看出,页面整体为纵向布局。各个分类下的文章列表,在手机端是以纵向列表样式显示,在折叠屏展开态和pad上是以不同的样式横向布局,因此整体使用纵向List布局,对于每个分类下的子卡片按照不同的样式分别使用Swiper和List组件进行布局。 +

+

+ 下边以应用架构设计模块做简要说明。应用架构设计在手机端是以纵向列表形式展开,折叠屏展开态属于横向列表,并且屏幕内固定展示两个,第三个显示在屏幕内显示固定宽度,结合这些特点,选择使用Swiper组件进行布局。 +

+
// features/exploration/src/main/ets/component/ArchitectureCard.ets
+Swiper() {
+      // ...
+}
+.nextMargin(new BreakpointType<Length>({
+   sm: 0,
+   md: $r('sys.float.padding_level6'),
+   lg: $r('sys.float.padding_level6')
+}).getValue(this.globalInfoModel.currentBreakpoint))
+.size(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ?
+   { height: DiscoveryConstant.ARCHITECTURE_ITEM_HEIGHT * this.discoverContents.length } : 
+   { height: $r('app.float.architecture_item_height') })
+.disableSwipe(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) // 纵向布局时全部铺开无需滑动
+.vertical(this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM) // 手机端设置为纵向布局, 折叠屏展开态和pad设置为横向滑动
+.displayCount(
+   new BreakpointType({
+     sm: this.discoverContents.length, // 纵向滑动时展示数量为数据源的总数量
+     md: CommonConstants.LANE_MD,
+     lg: CommonConstants.LANE_LG
+   }).getValue(this.globalInfoModel.currentBreakpoint)
+)
+
+
+
+
+

1.2.3.5 组件详情页

+
+

+ 组件详情页包含组件效果预览区域、组件属性调整区域、示例代码和相关推荐,具体效果见下表。

+ +
+ + + + + + + + + + + + + + + +
+

sm

+
+

md

+
+

lg

+
+

+
+

+
+

+
+
+

+ 手机端页面属于是上下布局,在折叠屏展开态和pad上组件预览区域和其他区域属于左右布局,因此,页面整体布局使用弹性布局Flex组件以便能在不同尺寸的设备上灵活设置布局方向。

+

+ 将页面内容分为组件预览区域和其他区域两部分,手机端使用上下布局样式,设置Flex组件的direction参数为FlexDirection.Column,折叠屏展开态和pad上通过设置direction参数为FlexDirection.Row来实现左右布局。 +

+
// features/componentlibrary/src/main/ets/component/DetailContentView.ets
+Flex({
+      direction: this.globalInfoModel.currentBreakpoint === BreakpointTypeEnum.SM ? FlexDirection.Column :
+      FlexDirection.Row
+    }) {
+      Column() {
+        // ...
+      }
+      Column() {
+        // ...
+      }
+}
+
+
+
+

1.2.3.6 资源使用

+
+

HMOS世界中,各个页面在多端显示的效果不同,使用的资源也需要随着屏幕尺寸变化,推荐使用媒体查询获取资源的方式来做适配,借助媒体查询中的断点功能做简单的封装。下面以横向断点做简单介绍。

+

+ 创建BreakpointType类,通过getValue()方法获取对应的资源值。在使用时,创建不同的资源文件传入BreakpointType代表sm、md和lg断点下的资源值,实现应用窗口大小变化时的不同效果。 +

+
// common/src/main/ets/util/BreakpointSystem.ets
+export class BreakpointType<T> {
+  xs: T;
+  sm: T;
+  md: T;
+  lg: T;
+
+  constructor(param: BreakpointTypes<T>) {
+    this.xs = param.xs || param.sm;
+    this.sm = param.sm;
+    this.md = param.md;
+    this.lg = param.lg;
+  }
+
+  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;
+    }
+    return this.lg;
+  }
+}
+

例如断网状态的暂未图片在不同设备上显示的尺寸大小不一样。效果如下:

+ +
+ + + + + + + + + + + + + + + +
+

sm

+
+

md

+
+

lg

+
+

+
+

+
+

+
+
+

代码实现如下:

+
// common/src/main/ets/view/NoNetworkView.ets
+export function NoNetworkView(breakpoint: BreakpointTypeEnum, handleReload?: () => void) {
+  GridRow({ columns: { sm: ColumnEnum.SM, md: ColumnEnum.MD, lg: ColumnEnum.LG } }) {
+    GridCol({
+      span: { sm: CommonConstants.SPAN_4, md: CommonConstants.SPAN_6, lg: CommonConstants.SPAN_6 },
+      offset: { sm: 0, md: 1, lg: CommonConstants.SPAN_3 }
+    }) {
+      Column() {
+        Column() {
+          Image($r('app.media.ic_failure'))
+            .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)
+           // ...
+        }
+        // ...
+      }
+    }
+  }
+}
+
+
+
+
+
+

1.3 总结

+
+

+ 本文通过组件、样例、实践和组件详情页等几个典型场景,介绍了HMOS世界中关于“一多”开发的适配过程。由上文可以看出几种常用组件在一多适配过程中适用的典型场景如下:

+
    +
  • List:每个item宽高一致,只有不同断点下展示的列数不同。
  • +
  • WaterFlow: 每个item宽度一致,高度不同,在不同断电下展示的列数不同。
  • +
  • GridRow:每个item宽度不同,高度一致,在不同断点下展示的列数不同。
  • +
  • Flex:在不同断点下子组件的布局方向不同。
  • +
+

文章中只做了关键代码的简要展示,详细代码可参考项目源码。

+
+
+
+ + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..b807eddbb7ba2c80249587a8bccd6936cb2e68b8 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/2.gif b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/2.gif new file mode 100644 index 0000000000000000000000000000000000000000..b28c68ff69035aebd1e51aa8f93541691e5df49b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/2.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/3.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/3.png new file mode 100644 index 0000000000000000000000000000000000000000..ac740e2376c2c91564c844115ad5bd48b5f99304 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/4.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..a40953cc1a0405b001256aaa683b2280a91f8b40 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/4.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/5.gif b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/5.gif new file mode 100644 index 0000000000000000000000000000000000000000..5813ece9f43bc3e3f139a2bc985feb5623a05f10 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/5.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/6.gif b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/6.gif new file mode 100644 index 0000000000000000000000000000000000000000..60160a548d78f904be0085a301baee4a498db30a Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/6.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/7.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/7.png new file mode 100644 index 0000000000000000000000000000000000000000..bc403f335b99ef786d19ba907d5351e9977bfd85 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/7.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/8.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/8.png new file mode 100644 index 0000000000000000000000000000000000000000..05570200aa504c278673dcce89b615374a62cee3 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/8.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/9.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/9.png new file mode 100644 index 0000000000000000000000000000000000000000..68195af55e691f4daf1ca4b749985be99c017f83 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/ManulImages/9.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/index.html new file mode 100644 index 0000000000000000000000000000000000000000..2258cc8eed769bed6f44c530511e3d8cefbc94cf --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/native-safety/index.html @@ -0,0 +1,319 @@ + + + + + + + Picker安心取,用户主导安全新体验 + + + + + +
+

1 Picker安心取,用户主导安全新体验

+
+ +
+

1.1 隐私保护与安全概述

+
+

+ 在个人隐私安全的重要性日益凸显的时代,HarmonyOS在隐私和数据保护上的创新,重构了系统级安全体系,打造了安全能力,让操作系统安全告别旧机制,迎来新秩序。

+

系统通过提供安全控件系统Picker两种方式,使得应用能够便捷地访问系统资源。这两种方法均依赖于系统的独立进程来实现,当应用拉起系统Picker或展示安全控件时,必须依赖用户的主动操作来获取资源或结果。这一流程避免了应用额外申请权限,同时,由于用户的积极参与,进一步增强了用户隐私和安全的保护 +

+
+
+
+
+

1.2 安全控件

+
+

+ 安全控件是系统提供的一组系统实现的ArkUI组件,应用集成这类组件就可以实现在用户点击后自动授权,而无需弹窗授权。它们可以作为一种“特殊的按钮”融入应用页面,实现用户点击即许可的设计思路。

+

相较于动态申请权限的方式,安全控件可基于场景化授权,简化开发者和用户的操作,主要优点有:

+
    +
  1. 用户可掌握授权时机,授权范围最小化。
  2. +
  3. 授权场景可匹配用户真实意图。
  4. +
  5. 减少弹窗打扰。
  6. +
  7. 开发者不必向应用市场申请权限,简化操作。
  8. +
+
+
+
+

1.2.1 安全控件运行机制

+
+

运行机制整体方案由安全控件UI组件、安全控件管理服务、安全控件增强组成:

+
    +
  • UI组件:实现了固定文字图标的样式,便于用户识别,同时提供了相对丰富的定制化能力,便于开发者定制。
  • +
  • + 控件管理服务:提供控件注册管理能力、控件临时授权机制、管理授权生效周期,确保应用后台、锁屏下无法注册使用安全控件。
  • +
  • + 安全增强:实现了地址随机化、挑战值检查、回调UI框架复核控件信息、调用者地址检查、组件防覆盖、真实点击事件校验等机制,防止应用开发者通过混淆、隐藏、篡改、仿冒等方式滥用授权机制,泄露用户隐私。
  • +
+

开发者调用接口时,运作流程如图所示。

+
图1-1 安全控件运行机制
+
+
+
+

1.2.2 安全控件汇总

+
+
+

1.2.2.1 粘贴控件

+
+

在应用集成粘贴控件后,用户点击粘贴控件,应用读取剪贴板数据时不会弹窗提示。可以用于任何应用需要读取剪贴板的场景,避免弹窗提示对用户造成干扰。 +

+

+ 例如,用户在应用外(如短信)复制了验证码,要在应用内粘贴验证码。用户原来在进入应用后,还需要长按输入框、在弹出的选项中点击粘贴,才能完成输入。而使用粘贴控件,用户只需进入应用后直接点击粘贴按钮,即可一步到位。 +

+
图1-2 粘贴控件效果
+
+
+

开发者应注意,使用了ArkUI的输入框会自动集成长按菜单栏的粘贴控件。

+
+
+
+
+
+

1.2.2.2 保存控件

+
+

保存控件是一种特殊的安全控件,它允许用户通过点击按钮临时获取存储权限,而无需通过权限弹框进行授权确认。可以用于任何应用需要保存文件到媒体库的场景(保存图片、保存视频等)。 +

+

+ 集成保存控件后,当用户点击该控件时,应用会获得10秒内访问媒体库特权接口的授权。这适用于任何需要将文件保存到媒体库的应用场景,例如保存图片或视频等。

+
图1-3 保存控件效果
+
+ +
+

+ 保存控件仅限ArkUI页面可进行集成,若采用第三方组件如Flutter等则无法实现相应功能,此类应用可查看保存用户文件以实现需求。

+
+
+
+
+
+
+
+

1.3 系统Picker

+
+
+
+

1.3.1 Picker介绍

+
+

+ 系统picker是拉起系统资源的一种方式,由于系统Picker已经获取了对应权限的预授权,开发者使用系统Picker时,无需再次申请权限也可临时受限访问对应的资源。

+

使用系统Picker组件拉起系统应用的场景主要有:联系人Picker(Contacts + Picker),地图Picker,相机Picker(Camera + Picker),扫码Picker,卡证识别Picker,文档扫描Picker,文件Picker,音频Picker和照片Picker(PhotoViewPicker)等,详细可参考拉起系统应用

+
+
+
+

1.3.2 Picker汇总

+
+
+

1.3.2.1 照片Picker

+
+

+ PhotoViewPicker支持开发者通过特定接口拉起系统图库,用户自行选择待分享的资源,然后最终完成分享。该Picker承自photoAccessHelper相册管理模块,该模块提供相册管理模块能力,包括创建相册以及访问、修改相册中的媒体数据信息等。

+

同时PhotoPicker组件也可用于访问图片/视频,在用户选择所需要的图片资源后,直接返回该图片资源,而不需要授予应用读取图片文件的权限,即可完成图片或视频文件的访问和读取。 +

+
图1-4 picker拉起图库
+

参考案例

+ +
+
+
+

1.3.2.2 联系人Picker

+
+

Contacts Kit提供联系人Picker(Contacts + Picker),用于拉起联系人应用,读取联系人数据人。

+

Contacts + Picker:当用户选择联系人的时候,通过Picker的方式,拉起联系人列表,引导用户完成界面操作,接口本身无需申请权限

+
+
+

+ 当前能力受限开放,需要申请受限开放权限ohos.permission.READ_CONTACTS或ohos.permission.WRITE_CONTACTS。该权限通常不允许三方应用申请,仅符合指定场景的应用可申请该权限。

+
+
+
+
+
+

1.3.2.3 文件Picker

+
+

DocumentViewPicker适用于文件类型文件的选择与保存。DocumentViewPicker对接的选择资源来自于FilePicker, + 负责文件类型的资源管理,文件类型不区分后缀,比如浏览器下载的图片、文档等,都属于文件类型。

+

参考案例

+

+ 文件管理案例介绍了如何使用DocumentViewPicker的save()方法向用户目录下创建一个文件并返回它的uri,和select()方法择想要访问的文件,并返回其uri,详情可参考《实现文件管理功能 - 6 用户目录的文件读写》

+
+
+
+

1.3.2.4 相机Picker

+
+

Camera + Kit提供了相机picker模块,本模块提供相机拍照与录制的能力。应用可以自行选择媒体类型实现拍照和录制的功能。该类接口,需要应用在界面UIAbility中调用,否则无法拉起cameraPicker应用,具体可参考Camera Picker

+

+ 如果开发者仅是需要拉起系统相机拍摄一张照片、录制一段视频,可直接使用CameraPicker,无需申请相机权限,直接拉起系统相机完成拍摄。

+

开发者需在release模式下调用系统相机(CameraPicker),在debug模式下会显示异常。

+
+
+
+

1.3.2.5 卡证识别Picker

+
+

卡证识别控件提供身份证(目前仅支持中国大陆二代身份证,且不包含民汉双文身份证)、行驶证、驾驶证、护照、银行卡等证件的结构化识别服务,满足卡证的自动分类功能,系统可自动判断所属卡证类型并返回结构化信息和卡证图片信息。 +

+

对于需要填充卡证信息的场景,如身份证、银行卡信息等,可使用卡证识别控件读取OCR(Optical + Character Recognition)信息,将结果信息返回后进行填充。支持单独识别正面、反面,或同时进行双面识别。

+
图1-5 卡证识别
+
+
+

+ 使用该控件会创建弹窗,并以全模态形式展示。因此,该控件被拉起或退出时均会触发接入页面的生命周期变化,拉起时会触发页面的onPageHide,退出时则触发页面的onPageShow。

+
+
+

参考案例

+

场景化视觉服务文章介绍了卡证识别能力,包括识别身份证、行驶证、驾驶证等,详情可参考《机器学习-场景化视觉服务》。

+
+
+
+

1.3.2.6 文档扫描Picker

+
+

文档扫描控件提供拍摄文档并转换为高清扫描件的服务。仅需使用手机拍摄文档,即可自动裁剪和优化,并支持图片保存和分享;同时支持拍摄或从图库选择图片识别表格,生成表格文档。 +

+

可广泛用于教育办公场景,扫描文档、票据、课堂PPT和书籍等输出图片供用户完成发送、存档等操作。

+
图1-6 文档扫描
+

参考案例

+

+ 场景化视觉服务文章同时介绍了文档扫描能力,包括拍摄文档或从图库中选择文档图片,将其转换为高清扫描件;拍摄或从图库选择图片识别表格,生成表格文档,详情可参考《机器学习-场景化视觉服务》。

+
+
+
+

1.3.2.7 地图Picker

+
+

Map Kit的地图Picker,提供地点详情展示控件、地点选取控件、区划选择控件。

+

地点详情展示

+

无需自己开发地图页面,可快速实现查看地点详情展示功能,具体内容可参考《地点详情展示》。

+
图1-7 地点详情展示
+

地点选取

+

无需自己开发地图页面,可快速实现地点选取的能力,具体内容可参考《地点选取》。

+
图1-8 地点选取
+

区域选择

+

区划选择控件可加载全球或指定国家的区划信息,支持查看选中区划的下级区划、支持推荐热门区划,具体内容可参考《区域选择》。

+
图1-9 区域选择
+
+
+
+

1.3.2.8 音频Picker

+
+

AudioViewPicker用于访问、保存公共目录的图片或视频文件,对接的选择资源来自于FilePicker。参考链接如下:

+

1、选择音频类文件

+

2、保存音频类文件

+
+
+
+
+
+

1.4 总结

+
+

+ 本文重点介绍了通过安全控件和系统Picker实现安全功能的常见处理方法,旨在为开发者提供实践思路和初步实现方案。需要注意的是,HarmonyOS的安全处理方式远不止上述内容,更多详细信息可参考相关指南以及最佳实践文档。开发者应根据应用类型、用户定位以及适用的隐私法规,采取更全面、具体的措施来确保应用安全。

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..1bb428124c993929de9a4071e13f325d9cbb0491 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/10.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/10.png new file mode 100644 index 0000000000000000000000000000000000000000..08d63eb1d152b4db472d405a17c73fa0431046ea Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/10.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/11.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/11.png new file mode 100644 index 0000000000000000000000000000000000000000..d8517fb5a8239861eb56305a4bc1b9e07399629d Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/11.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/12.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/12.png new file mode 100644 index 0000000000000000000000000000000000000000..5320e616a12f68df6d271765d1069800a95f3fe4 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/12.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/13.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/13.png new file mode 100644 index 0000000000000000000000000000000000000000..16e0eb42b3dcab3588b646cd9f6480edfc55150e Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/13.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/14.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/14.png new file mode 100644 index 0000000000000000000000000000000000000000..5864655be7bf6352435168407be61b81e7f30dd2 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/14.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/15.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/15.png new file mode 100644 index 0000000000000000000000000000000000000000..451a4e17070080e49a670216f4189053bb4ddc23 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/15.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/16.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/16.png new file mode 100644 index 0000000000000000000000000000000000000000..5c186fb3661f9350222a06971a069e62f22e3c1b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/16.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/17.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/17.png new file mode 100644 index 0000000000000000000000000000000000000000..46a6316273d728fb2da71126abc51d3ae8c19dce Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/17.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/18.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/18.png new file mode 100644 index 0000000000000000000000000000000000000000..a48be7b4ab2dc1f2661d8071304e6a59f18cd0bd Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/18.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/19.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/19.png new file mode 100644 index 0000000000000000000000000000000000000000..60fd3a31cfbd745d224fc321304c3835f0a4b1cf Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/19.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/2.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..30ea8cd11d6e410059cfc497218ec7c9a8cd7879 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/20.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/20.png new file mode 100644 index 0000000000000000000000000000000000000000..51dcc3a6334e2706b59b1b06b116cdddaf8f2b32 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/20.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/21.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/21.png new file mode 100644 index 0000000000000000000000000000000000000000..241d130bdbb5beed193a7cd079b3e8af656af288 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/21.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/22.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/22.png new file mode 100644 index 0000000000000000000000000000000000000000..f476f7f072f0d9fefcea646870bceeb8610ec66e Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/22.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/23.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/23.png new file mode 100644 index 0000000000000000000000000000000000000000..38adb245376cac34fb3b87e261c6e384429e1e8b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/23.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/24.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/24.png new file mode 100644 index 0000000000000000000000000000000000000000..3cdad834d16b3a6abd5d2a6bb36f368e67953cb1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/24.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/25.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/25.png new file mode 100644 index 0000000000000000000000000000000000000000..29005f2986279a634d71eb75491e74ab1833741b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/25.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/26.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/26.png new file mode 100644 index 0000000000000000000000000000000000000000..d9696269cb26dad5c49c909b2c89f0330b50c25e Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/26.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/27.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/27.png new file mode 100644 index 0000000000000000000000000000000000000000..e2ff0a9fa438023043ac1729b9dc651b1e9c4711 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/27.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/28.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/28.png new file mode 100644 index 0000000000000000000000000000000000000000..237c64c7b3ada7643b253f7f5acddf43a745c6c3 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/28.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/29.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/29.png new file mode 100644 index 0000000000000000000000000000000000000000..141fa4422b0bb33c335c106bf3d4250b5688f0f1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/29.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/3.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/3.png new file mode 100644 index 0000000000000000000000000000000000000000..ff0298b51372799f2675def7a18417eec88e5c17 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/30.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/30.png new file mode 100644 index 0000000000000000000000000000000000000000..358c935f7518451181e1bcbb7bae3788b4cb9db8 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/30.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/31.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/31.png new file mode 100644 index 0000000000000000000000000000000000000000..894b64a7dd0e256b07c267af7528df45bf20bd06 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/31.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/32.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/32.png new file mode 100644 index 0000000000000000000000000000000000000000..0f9c9bcc0cff59fd93ba999050923493bf539316 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/32.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/33.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/33.png new file mode 100644 index 0000000000000000000000000000000000000000..690196773283308a3d4b47ba14ee900674f4b339 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/33.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/34.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/34.png new file mode 100644 index 0000000000000000000000000000000000000000..3519b576b175a6914ee6f6746be9783541e262ab Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/34.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/35.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/35.png new file mode 100644 index 0000000000000000000000000000000000000000..39939c67155c1f72f76900aaf2012178cb39e351 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/35.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/36.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/36.png new file mode 100644 index 0000000000000000000000000000000000000000..12a86fc96e9aaa64b7f3cd4be9c65194827700a7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/36.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/37.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/37.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae91a31ef1461c433f2696770bbd845a3870370 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/37.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/38.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/38.png new file mode 100644 index 0000000000000000000000000000000000000000..47ce1e01a08ce4ebd394dc802aae408f7cfc53df Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/38.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/39.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/39.png new file mode 100644 index 0000000000000000000000000000000000000000..fd0048818171a86b99686a003b34a30e53e9de60 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/39.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/4.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..4069d633736b604a887c44c0388f2b0b7a759cf2 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/4.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/5.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/5.png new file mode 100644 index 0000000000000000000000000000000000000000..5934028bd8ca5e6735e3fef76427f3fc06a33f58 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/5.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/6.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/6.png new file mode 100644 index 0000000000000000000000000000000000000000..5efc8ca0190ed3d7d1339ff8eb1d4cee3c3cd6aa Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/6.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/7.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/7.png new file mode 100644 index 0000000000000000000000000000000000000000..fd05c382ecb6d0bc1b748be5d4a4ce2beacffe97 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/7.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/8.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/8.png new file mode 100644 index 0000000000000000000000000000000000000000..10de41c0dc942c6c8c837d7d490cc5257327c5ea Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/8.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/9.png b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/9.png new file mode 100644 index 0000000000000000000000000000000000000000..92a40d2aa1fd159592f4102cdf30de95ee315547 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/ManulImages/9.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/index.html new file mode 100644 index 0000000000000000000000000000000000000000..48d212a71fdf64c2d7aa5816ce1617acfcc5b782 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/native-ux-design/index.html @@ -0,0 +1,130 @@ + + + + + + 多端UX设计 + + + + + +

1 多端UX设计

+
+

1.1 多端UX设计

+

多端UX设计

+

+

+

+

+
+
+

1.2 为什么需要「 多端的UX设计 」

+

常见宽屏上的体验痛点:

+
  • 大图大字体、信息量少、内容截断、内容变形错乱、不支持横屏、留白过大。
  • 想要做好宽屏设计,但不知道怎样实现体验增值。
+

+

+

+
+
+

1.3 怎样做「 多端的UX设计 」

+

了解多端的物理形态差异,及人因上的影响,考虑最佳的交互方式、信息量&内容布局 基于宽屏上“更多显示”、“更加沉浸”、“更高效率”、“悬停创新”的设计原则,挖掘增值体验。

+

+
+
+

1.4 基础要求

+

+

基础要求要求

+

+

基础要求(应用UX体验标准)

+

+

+
+
+

1.5 响应式布局

+

+

响应式布局方法

+

+

案例1

+

沉浸广告图的自适应延伸布局:影音娱乐、电商、生活服务、金融理财等需要打造沉浸感或营销氛围的场景,可使用沉浸广告图 背景和内容分层,背景横向铺满,广告元素延伸布局,横屏不超过0.5倍屏幕高度,竖屏不超过0.4倍屏幕高度。

+

+

宽屏设备上,沉浸式Banner切换为卡片Banner:

+

+

卡片广告图的延伸布局+挪移布局:

+

+

案例2

+

左文右图列表的重复布局:

+

+

上文下图/大视频的挪移布局:

+

+

列表变宫格的布局创新:

+

+
+
+

1.6 增值体验

+

+

增值体验的设计方法

+

+

显示更多

+

边看边评:在宽屏设备上进行图文阅读时,右侧提供评论、相关推荐、关键词解析等更多相关辅助信息。

+

+

更加沉浸

+

沉浸观影 折叠屏上全屏播放视频不旋转,不中断观影体验。

+

+

弹幕吸顶:弹幕尽量在留黑的位置显示,减少对视频画面的遮挡。

+

+

侧边面板:播放视频时,在侧边面板内执行其他相关操作,视频画面被推挤,避免对视频画面的遮挡。

+

+

更高效率

+
  • 高效分栏:金融、电商、生活服务、新闻资讯类应用部分页面,通过分栏提升效率。
+

+
  • 应用内分屏:办公类场景,应用内分屏实现文档协同;IM对话场景,打开网页、元服务、文档、通话时提供应用内分屏,实现边聊边看体验;购物场景时通过应用内分屏,实现购物比价功能。
+

+

悬停创新

+

悬停后,显示类内容上移,交互类内容下移,且布局合理。

+

+

+

+

悬停后,自动从显示歌词变为显示MV,提供新的使用体验。

+

+

+

悬停适配

+

交互类控件,在下半屏易交互区域显示:

+

+

上下文关联的控件,和其触发元素在同一侧屏幕内显示:

+

+
+
+

1.7 典型案例

+

+

典型案例-影音娱乐长视频

+

+

典型案例-影音娱乐短视频

+

+

典型案例-电商购物

+

+

+
+
+
+ + + + diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/1.gif b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/1.gif new file mode 100644 index 0000000000000000000000000000000000000000..8cd9d32b12d39b5d3f7e62fac959b3b7e08233c2 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/1.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/2.gif b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/2.gif new file mode 100644 index 0000000000000000000000000000000000000000..a2582fc9b665c84041c94b3ded1aa0ee71830922 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/2.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/3.gif b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/3.gif new file mode 100644 index 0000000000000000000000000000000000000000..f536f194437307c117a1e964f2a6402ccf15a191 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/3.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/4.gif b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/4.gif new file mode 100644 index 0000000000000000000000000000000000000000..4bcd3606626d7079921148f16f83ceba071b6be5 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/4.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/5.gif b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/5.gif new file mode 100644 index 0000000000000000000000000000000000000000..9c0a4f6219fc3dbbc3b5cadff7c64b8396bee85f Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/5.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/6.gif b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/6.gif new file mode 100644 index 0000000000000000000000000000000000000000..7d944d05a0636f6b5f7e952442b497dc439939d8 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/ManulImages/6.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/index.html new file mode 100644 index 0000000000000000000000000000000000000000..e1bb2b29fd532082fcf6faa26747e8b943bb5720 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/shared-element-transition/index.html @@ -0,0 +1,197 @@ + + + + + + + 一镜到底,畅享无界丝滑视觉之旅 + + + + + +
+

1 一镜到底,畅享无界丝滑视觉之旅

+
+ +
+

1.1 什么是一镜到底

+
+

+ 在日常使用手机或其他智能设备时,我们常常会遇到这样的情况:当从一个页面切换到另一个页面,或从桌面打开应用程序时,画面会突然“卡顿”一下,就像电影播放时突然丢了帧,原本沉浸的体验被瞬间打破。然而,HarmonyOS + 的一镜到底动效却能让这一切变得截然不同。下面我们通过一个简单的图片切换场景来更直观地对比常规效果和一镜到底效果的区别。

+
图1-1 一镜到底动效
+
+
+
+
+

1.2 一镜到底动效设计

+
+

一镜到底是一种通过共享元素进行转场的编排方式,有助于提升用户操作任务的效率,增强视觉的流畅感,是转场设计中重点推荐的技法。

+

共享元素

+

+ 共享元素一般是转场前后持续存在的界面元素,即上文提到的持续元素,是在转场发生后希望用户关注到的焦点元素,它增强了转场的连续感。

+

+

此案例中,搜索框是共享元素

+

共享容器

+

+ 当一组元素在过渡时包含明确的边界,可使用容器来让转换过程有连续感。容器通过大小、高度、圆角等属性进行补间过渡转换,容器内的元素可通过淡入淡出或共享元素的手法进行过渡。

+

+

共享动势

+

+ 无中间属性,无法通过补间变化来实现柔和过渡,需要提取出可用的共享转换属性,来实现前后的平滑过渡。常用的共享运动属性有位移、缩放、旋转等。

+

共享缩放运动

+

+

共享旋转运动

+

+

相关文档:转场动效

+
+
+
+
+

1.3 如何适配一镜到底

+
+

一镜到底的动效有多种实现方式,在实际开发过程中,应根据具体场景选择合适的方法进行实现。

+

以下是不同实现方式的对比:

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+

一镜到底实现方式

+
+

特点

+
+

适用场景

+
+

不新建容器直接变化原容器

+
+

不发生路由跳转,需要在一个组件中实现展开及关闭两种状态的布局,展开后组件层级不变。

+
+

适用于转场开销小的简单场景,如点开页面无需加载大量数据及组件。

+
+

新建容器并跨容器迁移组件

+
+

+ 通过使用NodeController,将组件从一个容器迁移到另一个容器,在开始迁移时,需要根据前后两个布局的位置大小等信息对组件添加位移及缩放,确保迁移开始时组件能够对齐初始布局,避免出现视觉上的跳变现象。之后再添加动画将位移及缩放等属性复位,实现组件从初始布局到目标布局的一镜到底过渡效果。 +

+
+

适用于新建对象开销大的场景,如视频直播组件点击转为全屏等。

+
+

使用geometryTransition共享元素转场

+
+

+ 利用系统能力,转场前后两个组件调用geometryTransition接口绑定同一id,同时将转场逻辑置于animateTo动画闭包内,这样系统侧会自动为二者添加一镜到底的过渡效果。

+
+

+ 系统将调整绑定的两个组件的宽高及位置至相同值,并切换二者的透明度,以实现一镜到底过渡效果。因此,为了实现流畅的动画效果,需要确保对绑定geometryTransition的节点添加宽高动画不会有跳变。此方式适用于创建新节点开销小的场景。 +

+
+
+
+
+

查看全部示例代码:共享元素转场 (一镜到底)

+
+
+
+
+
+
+

1.4 一镜到底场景案例

+
+
+
+

1.4.1 卡片、列表一镜到底

+
+

+ 在瀑布流或列表流布局中,当用户点击其中一个卡片或列表项时,应用将执行平滑的转场动画,引导用户从概览页面切换到详情页面。

+
图1-2 卡片、列表一镜到底
+

参考案例

+
    +
  1. 最佳实践《一镜到底动效开发实践》。
  2. +
  3. Sample示例代码《转场动效合集》。
  4. +
+
+
+
+

1.4.2 更多案例

+
+

查看更多案例以及源码请前往:转场动效合集

+
+
+
+
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..71206916e6a7a960f1a3d1eea38b895cc40835a3 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/2.gif b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/2.gif new file mode 100644 index 0000000000000000000000000000000000000000..433eec88c5350c54ad1331738e904e6416e39e1b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/2.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/3.gif b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/3.gif new file mode 100644 index 0000000000000000000000000000000000000000..0b5c049f6578484ab0a4e2796f95622e9c775911 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/3.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/4.gif b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/4.gif new file mode 100644 index 0000000000000000000000000000000000000000..ed82412101eaa9148f4107b656eecf397fdb1def Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/ManulImages/4.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/index.html new file mode 100644 index 0000000000000000000000000000000000000000..4b5df176f00336061619d686a5bf70d4e224fe77 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/smart-visual-recognition/index.html @@ -0,0 +1,388 @@ + + + + + + + AI识图,开启智能图像处理新纪元 + + + + + +
+

1 AI识图:开启智能图像处理新纪元

+
+ +
+

1.1 AI识图技术概览

+
+

+ 随着人工智能技术的飞速发展,AI识图已成为智能设备感知物理世界的核心能力之一。本节将从AI识图场景落地和技术实现两个维度,解析HarmonyOS如何通过AI识图技术重构图像处理的范式,为开发者与用户提供更高效、精准、自然的交互体验。 +

+
+
+
+

1.1.1 场景介绍:HarmonyOS如何赋能AI识图

+
+

+ HarmonyOS作为新一代智能操作系统的代表,凭借其强大的生态系统和前沿的AI能力,正在重新定义智能图像处理的边界。AI识图功能作为HarmonyOS的核心亮点之一,集成了OCR(光学字符识别)、主体分割、实体识别、多目标识别等多项尖端技术,为用户和开发者提供了前所未有的智能化体验。 +

+

智能化交互,颠覆传统体验

+

+ AI识图功能能够轻松处理静态图片、视频暂停帧以及自定义渲染场景下的图像,为用户提供无缝的智能交互体验。无论是从图片中提取文字,还是识别图像中的主体并进行分割,AI识图都能以相当高的准确率和速度完成任务,彻底改变了传统图像处理的方式。 +

+

多场景覆盖,满足多样化需求

+

文字识别:用户只需长按图片中的文字,即可快速提取并复制文本内容,甚至触发电话号码、邮箱、网址等实体的快捷操作(如直接拨打电话或发送邮件)。

+

主体分割:用户长按图片中的主体,即可实现精准分割,轻松完成复制、分享或搜索操作。

+

+ 识图搜索:通过抠图功能,用户可以基于图像中的主体进行搜索,快速获取相关信息。例如,识别植物、动物、建筑物等目标,并以直观的ICON标识呈现搜索结果。

+

开发者友好,快速集成与创新

+

对于开发者而言,HarmonyOS的AI识图功能提供了简单易用的API和控件(如VisionImageAnalyzer),支持快速集成到各类应用中。无论是智能相册、安防监控,还是零售管理,开发者都能通过AI识图功能为用户提供更智能的服务,显著提升应用竞争力。 +

+
图1-1 AI示意图
+
+
+
+

1.1.2 技术核心:AI识图如何重塑图像处理

+
+

AI识图工作原理主要依赖于深度学习模型和服务端的强大计算能力。当应用调用此能力时,会触发一系列预训练的算法对输入图片进行分析,这些功能背后是一套复杂的神经网络架构,它们经过大量数据训练后能够准确地理解和解释视觉内容。为了保证性能与准确性之间的平衡,部分任务可能在设备本地完成,而另一些则会在云端服务器上处理(如识图搜索等)。 +

+

多功能集成,全面提升效率

+

+ AI识图功能不仅支持文字识别、主体分割等基础操作,还提供多目标识别,以及集成了AIButton等智能交互特性。具体如下:

+
    +
  • 多目标识别

    多目标识别可同时检测出给定图片中的各种物体,包括风景、动物、植物、建筑、树、人脸、表格、文本等位置,并框选出物体。

    +
  • +
  • AIButton

    AIButton集成了多种智能识别和操作功能的用户界面组件,根据图片内容动态显示(图片中存在文本且文本区域大于图片区域的5%时AIButton才会显示),主要用于提升用户在处理图片内容时的效率和体验,主要功能包括: +

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    能力

    +
    +

    触发条件

    +
    +

    说明

    +
    +

    实体识别与快捷操作

    +
    +

    电话号码、邮箱、网址、地址、时间等实体的显性下划线标识

    +
    +

    当图片中包含这些类型的文本信息时,AIButton + 会以显性的下划线形式标记出来。点击这些标记后,会弹出相应的快捷操作菜单(如拨打电话、发送邮件、打开网址等)。

    +
    +

    原图翻译

    +
    +

    系统设置语种与图片上文本语种不一致时进行翻译

    +
    +

    如果系统的当前语言设置与图片中的文本语言不同,AIButton + 可以将图片中的文本翻译为当前系统的语言。

    +
    +

    表格提取

    +
    +

    图片中存在表格时出现

    +
    +

    当图片中包含表格时,AIButton 会提供提取表格数据的功能,方便用户快速获取表格内容。 +

    +
    +
    +
    图1-2 AIButton示意图
    +
  • +
+
高精度与低功耗,技术赋能用户体验 +
    +
  • 高精度识别:基于深度学习的算法模型,能够精准识别图像中的复杂内容,甚至支持多语言文字识别和翻译。 +
  • +
  • 低功耗设计:优化后的算法和硬件协同设计,确保AI识图功能在移动设备上也能高效运行,延长设备续航时间。 +
  • +
+
+

开发者创新,打造智能化应用生态

+
    +
  • 提升用户体验:通过智能化的图像处理能力,用户能够更快速、更便捷地获取所需信息,显著提升使用效率。
  • +
  • 赋能开发者创新:提供丰富的API和控件,帮助开发者快速集成AI识图功能,打造更具竞争力的智能应用。 +
  • +
+
+
+
+
+

1.2 AI识图技术的使用与实践

+
+
+
+

1.2.1 快速上手:如何使用AI识图技术

+
+

+ AI识图功能的主开关入口位于基础控件API列表中,开发者仅需使用基础控件提供的使能接口即可开启功能。目前支持的基础控件包括ImageVideoXComponent,分别用于实现静态图片、视频暂停帧以及自定义渲染场景下的识图功能。

+

此外,HarmonyOS还提供了VisionImageAnalyzer(AI识图控件),开发者可以将其与Image、Video、XComponent控件配合使用,轻松实现多样化的识图功能: +

+
    +
  • Image控件:支持静态图片上的文字识别、主体分割等功能。
  • +
  • Video控件:支持视频播放暂停帧的识图分析。
  • +
  • XComponent控件:支持自定义渲染场景下的图像处理。
  • +
+

为了帮助开发者更好地掌握AI识图及文字识别能力,HarmonyOS官方推出了丰富的学习资源:

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

资源形式

+
+

资源名称

+
+

说明

+
+

视频课程

+
+

帮助快速构建各种文本识别应用

+
+

通过实例讲解,引导开发者快速上手文本识别功能

+
+

帮助应用打造场景化视觉服务

+
+

深入讲解具体场景下的视觉类AI能力,助力开发者实现场景化视觉服务

+
+

Codelabs课程

+
+

机器学习-基础视觉服务(ArkTS)

+
+

涵盖文字识别、主体分割等基础能力

+
+

机器学习-场景化视觉服务

+
+

聚焦场景化应用,提供从理论到实践的全面指导

+
+
+

+ 所有课程均附带完整源码及源码地址,开发者可随时下载学习、运行并查看实际效果。源码的开放不仅降低了学习门槛,还为开发者打造智能化、高效化的应用程序奠定了坚实基础。

+
+
+
+

1.2.2 优化技巧:如何最大化AI识图的价值

+
+

为更好利用识图功能,我们提出以下建议:

+
    +
  • + AI识图功能可帮助用户从图片上获取更多的信息(长按抠图,长按选取文本,长按实体识别等)。建议在大图预览场景下打开此功能,大图预览场景下用户对图片中的内容会更感兴趣,此时适时的提供识图服务契合用户体验场景,同时为用户提供最佳的识图交互体验。 +
  • +
  • + AI识图功能的AIButton的显示与图片中是否存在文本相关联,能够显性地提醒用户进行操作。开启AIButton会触发图片的预分析,导致一定的功耗开销,因此,建议开发者充分理解自身业务场景,预估目标用户图片内容分布,在兼顾用户图片浏览体验和提供更高阶AI识图功能体验的情况下按需提供AIButton。例如,业务本身是辅助用户高效提取图片中的文本内容,开启AIButton将会提升用户文本提取的体验。业务本身更偏向于图片编辑,也可隐藏AIButton。 +
  • +
+
+
+
+
+

1.3 场景案例

+
+
+
+

1.3.1 案例1:开发者世界中的AI抠图应用

+
+

+ HOMS客户端(开发者世界APP)中的AI抠图功能,通过Image控件实现了在静态图片上的精准识图与抠图。用户只需在开发者世界APP的体验页面,点击AI抠图大卡片进入详情页面,即可通过长按图片中的实体进行识别并抠图。识别后的实体不仅可轻松复制,还能便捷分享。此外,开发者世界APP还贴心提供了源代码预览功能,让开发者能够直接复制使用,极大地提升了开发效率。 +

+

开发关键点

+
    +
  1. (可选)首先,将待分析的图像资源文件转换为PixelMap对象。通常通过PixelMap加载资源文件更高效更灵活;
  2. +
  3. 在Image组件中,将enableAnalyzer属性设置为true,以启用AI分析功能。
  4. +
+

+ 经过上述步骤,Image组件将能够识别并抠出图片中的实体。用户可以通过长按图片中的实体进行识别,并轻松复制或分享识别结果。效果如下图:

+
图1-3 AI抠图效果图
+
+
+
+

1.3.2 案例2:AI辅助图文内容高效编创

+
+

本场景的图文编创流程主要通过Photo + Picker选取本地图片,然后对图片进行智能处理(OCR文字识别提取内容、智能抠图等),最后进行文字编创时可进行自由流转接续编辑和跨端获取相册或者相机拍摄内容。具体参考最佳实践《AI辅助图文内容高效编创》。

+

+ 其中,运用AI能力主要包括OCR文字识别、智能抠图,选取图片之后,可以浏览这些图片,并长按物体实现抠图,也可识别图片中的文字,用于后续文本内容编辑使用。效果如下图:

+
图1-4 长按识别文字与物体抠图
+

关键点说明

+
    +
  • 在Image组件设置enableAnalyzer属性,将实现文字识别和智能抠图。
  • +
  • + 文字识别:图片可文字识别时,通过点击图片内出现的识别按钮或者长按文字移动,会出现复制文本菜单与文字框选区域。
  • +
  • 智能抠图:长按图片中的物体,将出现抠图效果,菜单中可进行复制与分享。
  • +
+
+
+
+
+

1.4 总结

+
+

AI识图技术正在以惊人的速度改变我们与图像的交互方式,而HarmonyOS通过其强大的Vision + Kit和AI识图功能,为这一变革提供了坚实的技术支持。从文字识别、主体分割到多目标识别,AI识图不仅显著提升了用户体验,还为开发者提供了丰富的工具和API,赋能各行各业实现智能化升级。未来,随着技术的进一步演进,AI识图必将为我们的生活和工作带来更多智能化、便捷化的体验,开启智能图像处理的新纪元。 +

+

+
+
+
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/1.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/1.png new file mode 100644 index 0000000000000000000000000000000000000000..4b38dd05981683781838acbdd9231a3e1717a5c4 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/1.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/10.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/10.png new file mode 100644 index 0000000000000000000000000000000000000000..13b6ed0adb275620ede15b8d0f93fae110323ee1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/10.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/11.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/11.png new file mode 100644 index 0000000000000000000000000000000000000000..5a22cf957235d343b65232904c9c4bb232c6317a Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/11.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/12.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/12.png new file mode 100644 index 0000000000000000000000000000000000000000..a52e317ef11e825a4ab82cfa94d1b8a501c4e1d3 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/12.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/13.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/13.png new file mode 100644 index 0000000000000000000000000000000000000000..41348ec37f58796f80a74fd50ecf3521807d9917 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/13.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/14.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/14.gif new file mode 100644 index 0000000000000000000000000000000000000000..5e45f4e0e6a9e4d1b4336949f370ab522c17c867 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/14.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/15.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/15.gif new file mode 100644 index 0000000000000000000000000000000000000000..b0f69e075afb861cd1e0aa877f4334e2edbfa827 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/15.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/16.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/16.gif new file mode 100644 index 0000000000000000000000000000000000000000..b4538fd0dd6bfab4e2a08fa9ad64a1f6eb12574a Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/16.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/17.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/17.gif new file mode 100644 index 0000000000000000000000000000000000000000..84ca82e08039c934c2dabc9120bf682f70412a37 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/17.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/18.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/18.gif new file mode 100644 index 0000000000000000000000000000000000000000..6115127c93d1985da2ea048d40aeaa9fccf55797 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/18.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/19.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/19.gif new file mode 100644 index 0000000000000000000000000000000000000000..f9af92752ee4ce94ca0b6b59acefd7624c4f46a1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/19.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/2.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/2.png new file mode 100644 index 0000000000000000000000000000000000000000..7a96a940b3de5a950ce8cd2878583f1a76a4f2ab Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/20.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/20.gif new file mode 100644 index 0000000000000000000000000000000000000000..20c0ab0fd1154570d767cb8c1eea7ea404b7ebe7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/20.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/21.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/21.gif new file mode 100644 index 0000000000000000000000000000000000000000..ae77b405d47f228730c3d10c77f61b34ac72cb24 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/21.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/22.gif b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/22.gif new file mode 100644 index 0000000000000000000000000000000000000000..932ce1729ed65c054e4c3e7c5a3f715ef352c30b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/22.gif differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/23.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/23.png new file mode 100644 index 0000000000000000000000000000000000000000..dcdd11c177e210cc075b12fd1bfd7d9f215b18ba Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/23.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/24.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/24.png new file mode 100644 index 0000000000000000000000000000000000000000..d0aaa48e0e2c8e1113e7fd3d36515f306364b50a Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/24.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/25.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/25.png new file mode 100644 index 0000000000000000000000000000000000000000..b756a7981782e63e32643ec4b683365fc6752cd5 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/25.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/3.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/3.png new file mode 100644 index 0000000000000000000000000000000000000000..a46000a7ad96d0473e73c48815f960b0477d144e Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/4.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/4.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f3f5e84e001e587774f00ec5d10fbfbf56c51a Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/4.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/5.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/5.png new file mode 100644 index 0000000000000000000000000000000000000000..642ec9d374a6e2704c28267317b4e4c74262d854 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/5.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/6.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/6.png new file mode 100644 index 0000000000000000000000000000000000000000..37bb4f8994ba086eb9486d87f9ec4dd7e7157db6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/6.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/7.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/7.png new file mode 100644 index 0000000000000000000000000000000000000000..7acb44addaa0f2737b55bfc78a0cc53a15f22c36 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/7.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/8.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/8.png new file mode 100644 index 0000000000000000000000000000000000000000..dc98b7e6c2caec2f1624bf90ccc8b0db0fb7226b Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/8.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/9.png b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/9.png new file mode 100644 index 0000000000000000000000000000000000000000..42f92c1827520ad1fe048819181b7afcce27d968 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/ManulImages/9.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/index.html b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6321fbb11e18bebb6f7579585846ee7f18d66ed4 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/articles/ux-guidelines/index.html @@ -0,0 +1,260 @@ + + + + + + + 应用UX体验标准 + + + + + +
+

1 应用UX体验标准

+
+ +
+

1.1 应用UX体验标准

+
+

「应用UX体验标准」是什么?

+

优秀的应用体验来源于良好的UX设计。「HarmonyOS应用UX体验标准」从影响用户体验的各个维度定义了应用需要达到的准则要求,您的应用需至少满足下述标准要求中的必选条目,才可获准在应用市场上架。请在应用的设计开发过程中仔细检查应用的各个方面是否满足这些要求。 +

+

+

+
+
+
+
+

1.2 应用导航

+
+

系统返回

+

+ 所有界面都可以执行系统返回操作。除一级界面外,所有全屏界面均需要提供返回/关闭/取消按钮。(全屏沉浸式场景除外)

+

+
+
+
+
+

1.3 页面布局

+
+

布局基础要求

+

+ 应用支持在不同屏幕尺寸的设备上良好显示,图片、视频等界面元素应避免出现错位/截断/变形等问题。

+

+

挖孔区适配

+
    +
  • + 界面布局需要适配摄像头的挖孔区域,若重要信息或交互操作(例如底部页签/顶部页签、工具栏、标题栏、搜索框、输入框、悬浮按钮、横幅通知等)和挖孔区之间有遮挡,则需要局部避开挖孔区显示。
  • +
  • + 若重要信息或交互操作和挖孔区无遮挡,则无需避开挖孔区显示;悬浮类控件或功能(例如弹出框、侧边栏等),无需避开挖孔区显示;可以上下滚动的内容,例如列表、卡片等无需避开挖孔区显示。
  • +
  • 若应用支持横竖屏旋转,则横竖屏的界面布局均需满足以上挖孔适配要求。
  • +
+

+
+
+
+
+

1.4 人机交互

+
+

避免与系统手势冲突

+

+ 应用使用的手势应避免与系统手势冲突。不要设计可能跟系统手势冲突的手势操作,如屏幕边缘手势、指关节手势、三指及以上的手势。

+

+

典型手势时长设计

+

应用使用的典型手势设计需满足相关参数约束:

+
    +
  • 长按手势的接触时长应为500ms,时长在400ms-650ms内可调。
  • +
  • 双击手势的间隔时长应为70-400ms之间。
  • +
+

+

点击热区

+

+ 点击热区需满足最小尺寸要求:主要交互元素或控件的可点击热区至少为48vp×48vp(推荐),不得小于40vp×40vp(必须)。

+

+
+
+
+
+

1.5 视觉风格

+
+

色彩对比度

+

应用使用的色彩需满足最小对比度要求:

+
    +
  • 图标或标题文字与背景对比度大于3:1。
  • +
  • 正文文字与背景对比度大于4.5:1。
  • +
+

+

字体大小

+

应用的文字大小需满足最小字号要求:文本字号不小于12fp(推荐),最小不得小于8fp(必须)。

+

+

应用图标

+

+ 应用图标需分层,尺寸需满足规范要求:应用图标需分为前景图和背景图两层,尺寸要求为288px*288px,图标可见区域尺寸默认为192px*192px。

+

+

界面图标

+

应用的界面图标大小需满足最小尺寸要求:图标大小不小于12vp(推荐),最小不得小于8vp(必须)。

+

+

图标清晰度

+

图标需保证清晰可辩,无明显模糊、拉伸、压缩、锯齿等情况。

+

+
+
+
+
+

1.6 动效

+
+

层级转场

+

建议使用系统转场,页面转场采用左右位移的运动方式,不应单帧直接切换或者上下位移切换,曲线优先使用弹簧曲线。

+
图1-1 层级转场
+

新建转场

+

建议使用系统转场,页面转场采用上下位移的运动方式,不应单帧直接切换或者左右位移切换,曲线优先使用弹簧曲线。

+
图1-2 新建转场
+

编辑转场

+

建议使用系统转场,页面转场采用淡入淡出的过渡方式,不应单帧直接切换或者位移切换。

+
图1-3 编辑转场
+

搜索转场

+

+ 建议使用共享元素方式转场,搜索框作为持续存在的元素串联前后两个界面,其他元素可采用淡入淡出或者其他过渡方式,不应单帧直接切换或者非共享元素的方式转场。

+
图1-4 搜索转场
+

动效无缺失

+

+ 除特殊目的外,前后相邻界面的切换跳转或较大元素的进出场必须有转场动效过渡,不应单帧直接切换(含动效设计缺失和实现效果丢失两种情况)。

+
图1-5 动效无缺失
+

转场动效时长下限

+

+ 全屏页面的转场动效时长在8.5英寸以下的设备上不应短于200ms,在8.5至12英寸的设备上不应短于250ms,在12英寸以上的设备上不应短于300ms。

+
图1-6 转场动效时长下限
+

启动页

+

启动页填充:应用启动时(包括通过图标、服务卡片、通知卡片等入口)应尽量避免显示无内容的启动页(全黑、全白色彩填充)。 +

+

+ 启动页动效时长:有内容填充的启动页在全屏状态停留时长不建议超过3s,全黑、全白的空白无内容启动页在全屏状态停留时长不建议超过300ms。

+
图1-7 启动页
+

滑动过界反馈

+

界面滑动到边界位置时(上下滑动到顶部或底部,左右滑动到左边界或右边界),应该有反馈动效。

+
图1-8 滑动过界反馈
+

离手减速

+

对于可滑动页面,达到一定手速的滑动操作,在手离开屏幕后界面应继续移动,移动速度应该随时间缓慢下降,直至界面停止移动。 +

+
图1-9 离手减速
+
+
+
+
+

1.7 系统特性

+
+

底部导航条适配

+

手机、折叠屏、平板等设备屏幕底部有导航条,应用需对底部导航条进行适配:

+
    +
  1. + 应用内的底部固定控件、输入键盘、应用底部的悬浮按钮等均需要进行向上抬高,避免和导航条互相遮挡,也要避免导航条底部背景色与应用内底部背景色不融合,需要为导航条提供沉浸的背景效果。
  2. +
  3. 应用内的可滚动内容,需要能显示在导航条下方。当滚动到最底部时,要避免导航条遮挡导致最底部功能不可用。
  4. +
  5. 应用内的弹出框、半模态等控件,需要向上避让导航条,避免交互误触。
  6. +
  7. 沉浸式场景,例如游戏、全屏播放视频,导航条可自动隐藏,支持从底部上滑恢复显示导航条。
  8. +
+

+

多窗口交互

+
    +
  • 应用支持以悬浮窗模式运行,支持悬浮窗等比缩放调节;游戏、视频播放等沉浸式场景适配支持横向悬浮窗。
  • +
  • 应用支持上下分屏和左右分屏。
  • +
+

分屏运行时,需确保应用布局显示良好,无元素显示异常问题。

+

应用支持分屏比例调节,比例调节时需确保元素无变形挤压的情况出现。

+

+

深色模式

+

应用需支持深色模式显示,确保系统切换到深色模式后,界面以深色风格呈现,并且界面内没有因未适配导致的识别性问题。

+

+
+
+
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/config/articleUrlConfig.json b/products/phone/src/main/resources/resfile/articlecols/common/config/articleUrlConfig.json new file mode 100644 index 0000000000000000000000000000000000000000..c305c072704a91cd7190843d322e46ca30881104 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/config/articleUrlConfig.json @@ -0,0 +1,114 @@ +{ + "continue_1": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-continuation-guide#section17575828642", + "continue_2": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-connection-release#section88801052142317", + "continue_3": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-content-creation#section1973014141516", + "continue_4": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-continue-progess#section16702516134216", + "continue_5": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-continue-progess#section12439210434", + "continue_6": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-application-continue-progess#section3512987460", + "continue_7": "https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101705112214146396", + "continue_8": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-continuation-overview", + "continue_9": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/app-continuation-guide", + "continue_10": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_NEXT-DistributedMail", + "layer_1": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/store-moduleinstall_arkts", + "layer_2": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/foreword", + "liveview_1": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/liveview-introduction#section4266105713209", + "liveview_2": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/liveview-design-formula#section19663288592", + "liveview_3": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/liveview-design-formula#section168541692013", + "liveview_4": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/liveview-design-formula#section887024865911", + "liveview_5": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/live-view-kit-guide", + "multidevice_1": "https://developer.huawei.com/consumer/cn/doc/design-guides/design-concepts-0000001795698445", + "multidevice_2": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/introduction#工程结构", + "multidevice_3": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/page-development", + "multidevice_4": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/adaptive-layout", + "multidevice_5": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/responsive-layout", + "multidevice_6": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-layered-architecture-design", + "multidevice_7": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-multi-device-bp-practice", + "multidevice_8": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-gridrow", + "multidevice_9": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/responsive-layout#媒体查询", + "safety_1": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/security-component-overview", + "safety_2": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-picker", + "safety_3": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/security-component-overview#运作机制", + "safety_4": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/pastebutton", + "safety_5": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/savebutton", + "safety_6": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/save-user-file", + "safety_7": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/system-app-startup", + "safety_8": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-photoaccesshelper", + "safety_9": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/component-guidelines-photoviewpicker", + "safety_10": "https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101718767749261306", + "safety_11": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_CloudFoundationKit-ArkTS", + "safety_12": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/contacts-intro", + "safety_13": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/restricted-permissions#section09041234151715", + "safety_14": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-picker#documentviewpicker", + "safety_15": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_NEXT-FilesManger", + "safety_16": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/camera-overview", + "safety_17": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-camerapicker", + "safety_18": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/vision-cardrecognition", + "safety_19": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-VisionKit", + "safety_20": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/vision-documentscanner", + "safety_21": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-VisionKit", + "safety_22": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/map-introduction", + "safety_23": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/map-location-details", + "safety_24": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/map-location-selecting", + "safety_25": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/map-location-division", + "safety_26": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-picker#audioviewpicker", + "safety_27": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/select-user-file#选择音频类文件", + "safety_28": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/save-user-file#保存音频类文件", + "safety_29": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/system-security", + "safety_30": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-privacy-security", + "shared_1": "https://developer.huawei.com/consumer/cn/doc/design-guides/transition-animation-0000001750078488", + "shared_2": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-shared-element-transition", + "shared_3": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-one-shot-to-the-end", + "imageanalyzer_1": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/vision-imageanalyzer", + "imageanalyzer_2": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-text-recognition", + "imageanalyzer_3": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-subject-segmentation", + "imageanalyzer_4": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/vision-image-analyzer", + "imageanalyzer_5": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/vision-imageanalyzer", + "imageanalyzer_6": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/core-vision-object-detection", + "imageanalyzer_7": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/vision-image-analyzer#section18771735164320", + "imageanalyzer_8": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-image#enableanalyzer11", + "imageanalyzer_9": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-media-components-video#enableanalyzer12", + "imageanalyzer_10": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-xcomponent#enableanalyzer12", + "imageanalyzer_11": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/vision-image-analyzer", + "imageanalyzer_12": "https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101718769126547693", + "imageanalyzer_13": "https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101722224502553398", + "imageanalyzer_14": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-CoreVisionKit", + "imageanalyzer_15": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-VisionKit", + "imageanalyzer_16": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-image#pixelmap7", + "imageanalyzer_17": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-components-image#enableanalyzer11", + "imageanalyzer_18": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-content-creation#section4144207172817", + "imageanalyzer_19": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/vision-introduction", + "guildline_1": "https://developer.huawei.com/consumer/cn/doc/design-guides/ux-guidelines-overview-0000001760867048#section19443712173014", + "design_1": "https://developer.huawei.com/consumer/cn/doc/", + "design_2": "https://developer.huawei.com/consumer/cn/training/", + "design_3": "https://developer.huawei.com/consumer/cn/forum/", + "design_4": "https://developer.huawei.com/consumer/cn/activity/", + "empowerment_1": "https://developer.huawei.com/consumer/cn/doc/guidebook/harmonyecoapp-guidebook-0000001761818040", + "empowerment_2": "https://developer.huawei.com/consumer/cn/app/knowledge-map/", + "empowerment_3": "https://developer.huawei.com/consumer/cn/teaching-video/", + "empowerment_4": "https://developer.huawei.com/consumer/cn/codelabsPortal/getstarted/101718800110527001", + "empowerment_5": "https://developer.huawei.com/consumer/cn/codelabsPortal/serviceTypes", + "empowerment_6": "https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/application-dev-guide", + "empowerment_7": "https://developer.huawei.com/consumer/cn/doc/harmonyos-references/development-intro-api", + "empowerment_8": "https://developer.huawei.com/consumer/cn/samples/", + "empowerment_9": "https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-develop-once-deploy-everywhere", + "empowerment_10": "https://developer.huawei.com/consumer/cn/forum/", + "empowerment_11": "https://developer.huawei.com/consumer/cn/customerService/#/bot-dev-top/fap-top/faq-talk-top", + "uxexperirnce_1": "https://developer.huawei.com/consumer/cn/doc/design-guides/multi-device-responsive-design-0000001796704861", + "uxexperirnce_2": "https://developer.huawei.com/consumer/cn/doc/design-guides/ux-guidelines-overview-0000001760867048", + "uxexperirnce_3": "https://developer.huawei.com/consumer/cn/doc/design-guides/responsive-design-overview-0000001746498066", + "uxexperirnce_4": "https://developer.huawei.com/consumer/cn/design/resource/", + "quickstart_1": "https://developer.huawei.com/consumer/cn/codelabsPortal/getstarted/101718800110527001", + "quickstart_2": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-HelloWorld", + "quickstart_3": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-SwiperBanner", + "quickstart_4": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-BuildItem", + "quickstart_5": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-GridAndList", + "quickstart_6": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-BasicArchitectureDesignPart1", + "quickstart_7": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-BasicArchitectureDesignPart2", + "quickstart_8": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-ArkwebPageAdaptation", + "quickstart_9": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-DataDrivenUIUpdates", + "quickstart_10": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-SettingUpComponentNavigation", + "quickstart_11": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-TTS", + "quickstart_12": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-DevelopOnceDeployAnywhere", + "quickstart_13": "https://developer.huawei.com/consumer/cn/codelabsPortal/carddetails/tutorials_Next-DistributedFlow", + "main_domain": "https://developer.huawei.com" +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/config/giteeUrlConfig.json b/products/phone/src/main/resources/resfile/articlecols/common/config/giteeUrlConfig.json new file mode 100644 index 0000000000000000000000000000000000000000..c6a5f8162695afcb03527ddc8e5fd08e8d64b85c --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/config/giteeUrlConfig.json @@ -0,0 +1,9 @@ +{ + "continue_1": "https://gitee.com/harmonyos_samples/ContinuePublish", + "continue_2": "https://gitee.com/harmonyos_samples/graphic-creation", + "continue_3": "https://gitee.com/harmonyos_samples/continue-progress", + "liveview_1": "https://gitee.com/harmonyos_samples/live-view-kit_-sample-code_-clientdemo_-arkts#/harmonyos_samples/live-view-kit_-sample-code_-clientdemo_-arkts/blob/master/./实况窗各场景参数与模板映射关系指导.xlsx", + "shared_1": "https://gitee.com/harmonyos_samples/transitions-collection", + "shared_2": "https://gitee.com/harmonyos_samples/transitions-collection#转场动效合集", + "arkui_1": "https://gitee.com/harmonyos_samples/component-collection" +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/css/article.css b/products/phone/src/main/resources/resfile/articlecols/common/css/article.css new file mode 100644 index 0000000000000000000000000000000000000000..e8e4ba9e68e82840b364d83703ba5f490809794d --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/css/article.css @@ -0,0 +1,577 @@ +/* 清除所有元素的内外边距 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +/* 清除链接的下划线 */ +a { + text-decoration: none; +} + +br { + display: none; +} + +/* 清除表单元素的默认样式 */ +button, +input, +select, +textarea { + margin: 0; + padding: 0; + border: none; + outline: none; + background: none; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +/* 清除表格的边框间距 */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +/* 清除图片的默认下边距 */ +img { + display: block; + max-width: 100%; + height: auto; +} + +/* 清除docs封面 */ +table[class^='covertable'] { + display: none; +} + +/* 清除docs目录 */ +div[id^='tocd'] { + display: none; +} + +div > ul[class='ullinks'] { + display: none; +} + +/* 注册字体 */ +@font-face { + font-family: 'JetBrainsMonoNL-Regular'; + src: url(../fonts/JetBrainsMonoNL-Regular.ttf); +} + +html { + background-color: #fff; +} + +body { + margin: 0 1rem; + margin-top: 108px; + color: #00000099; + font-family: sans-serif; +} + +.nested0 { + max-width: 1200px; + margin: 0 auto; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + display: flex; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + color: #000000e6; +} + +h3, +h4, +h5 { + margin-top: 1.5rem; +} + +/* h1 */ +h1 { + margin-top: 1.5rem; + font-size: 1.5rem; +} + +/* h2 */ +h2 { + display: flex; + flex-direction: column; + position: relative; + margin-top: 3rem; + font-size: 1.25rem; + color: #0a59f7; +} + +h2::before { + display: block; + margin-bottom: 0.375rem; + content: ''; + width: 30px; + height: 30px; + background-image: url(../image/title_2.png); + background-size: cover; +} + +.nested1 { + margin-top: 3rem; + padding-bottom: 1rem; +} + +/* h3 */ +h3 { + font-size: 1.125rem; + color: #000000e6; + line-height: 1; +} + +h3::before { + display: inline-block; + content: ''; + margin-right: 8px; + width: 1.625rem; + height: 1.625rem; + background-image: url(../image/title_3.png); + background-size: cover; +} + +.nested3 { + padding-bottom: 1rem; +} + +/* h4 */ +h4 { + color: #000000e5; + padding-bottom: 0.25rem; +} + +/* 其他标题 */ +h4, +h5, +h6 { + font-size: 1rem; +} + +/* 隐藏标题前序号 */ +span[class^='topictitlenumber'] { + display: none; +} + +p, +.p { + margin-top: 1.5rem; + line-height: 1.875rem; + color: #00000099; + overflow-wrap: break-word; +} + +pre { + position: relative; + margin-top: 1.5rem; + padding: 0.75rem; + background-color: #f1f3f5; + border-radius: 1rem; +} + +pre code { + font-size: 0.75rem; + width: 100%; + background-color: transparent !important; + padding: 0 !important; + overflow-x: scroll; + scrollbar-width: none; + /* 滚动条 */ + line-height: 1.45rem; + font-family: 'JetBrainsMonoNL-Regular'; + transition: max-height 1s ease; +} + +.expand code { + max-height: 625rem; + overflow-y: auto; +} + +.fold code { + max-height: 32vh; + overflow-y: hidden; +} + +.foldButton { + position: absolute; + padding: 16px 0; + display: flex; + justify-content: center; + align-items: end; + width: 100%; + height: 80px; + left: 0; + bottom: 0; + font-size: 14px; + line-height: 16px; + background: linear-gradient(to top, #f1f3f5 80%, transparent 100%); + border-end-end-radius: 16px; + border-end-start-radius: 16px; + cursor: pointer; +} + +.foldButton::before { + display: inline-block; + content: ''; + width: 16px; + height: 16px; + background-image: url(../image/chevron_down_circle.svg); + background-size: contain; + margin-right: 8px; +} + +.divider { + position: absolute; + left: 12px; + bottom: 48px; + width: calc(100% - 24px); + height: 1px; + border-top: 1px solid #00000033; +} + +.fignone { + margin-top: 1.5rem; +} + +.topicbody > .note, +.topicbody > .caution { + margin-top: 1.5rem; +} + +.fignone > p { + margin-top: 1.5rem; +} + +a { + color: #0a59f7; + text-decoration: none; + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); + user-select: none; + -webkit-user-select: none; + -moz-user-focus: none; + -moz-user-select: none; +} + +ul { + padding-left: 1.25rem; +} +ol { + padding-left: 1.5rem; +} + +li { + margin-top: 0.5rem; + line-height: 1.875rem; + word-break: break-word; +} + +li p { + margin-top: 0rem; +} +.note, +.notice, +.caution, +.warning, +.danger { + position: relative; + padding: 1rem; + padding-top: 2rem; + font-size: 0.75rem; + border-radius: 1rem; +} + +.note p, +.notice p, +.caution p, +.warning p, +.danger p, +.note .p, +.notice .p, +.caution .p, +.warning .p, +.danger .p { + line-height: 1.25rem !important; + margin-top: 1rem; +} + +.note strong, +.notice strong, +.caution strong, +.warning strong, +.danger strong { + display: block; + padding: 0.25rem 0; +} + +.note { + background-color: rgba(10, 89, 247, 0.1); + border: 1px solid rgba(10, 89, 247, 0.3); +} + +.caution { + background-color: rgba(249, 160, 30, 0.1); + border: 1px solid rgba(249, 160, 30, 0.3); +} + +.note > img, +.notetitle > img { + position: absolute; + left: 1rem; + top: 1rem; + display: inline-block; + content: url(../image/book_pages.svg); + width: 1rem; + height: 1rem; +} + +.caution > img, +.cautiontitle > img { + position: absolute; + left: 1rem; + top: 1rem; + display: inline-block; + content: url(../image/exclamationmark_triangle.svg); + width: 1rem; + height: 1rem; +} + +.note::before { + position: absolute; + display: inline-block; + left: 2.25rem; + top: 1rem; + content: '说明'; + font-size: 0.875rem; + line-height: 1rem; + font-weight: bold; + color: #000000e6; +} + +.caution::before { + position: absolute; + display: inline-block; + left: 2.25rem; + top: 1rem; + content: '注意'; + font-size: 0.875rem; + line-height: 1rem; + font-weight: bold; + color: #000000e6; +} + +/* 列表内样式 */ +li pre, +li video, +li image, +li .note { + margin: 1.5rem 0 !important; +} + +.tablenoborder { + overflow-x: scroll; +} + +.tablecap { + display: none; + color: #000000e6; + font-size: 0.875rem; + margin-bottom: 0.75rem !important; +} + +table { + table-layout: fixed; + margin: auto; + margin-top: 1.5rem; + border-collapse: collapse; +} + +th, +td { + padding: 0.625rem 0.75rem; + overflow-x: auto; + word-wrap: break-word; + white-space: normal; +} + +th { + background: #f2f3f5; + height: 100%; + line-height: 100%; +} + +th p { + margin-top: 0; + font-size: 0.875rem; + line-height: 1.5rem; + color: #000000e6; +} + +td p { + display: inline-block; + margin-top: 0; + font-size: 0.75rem; + line-height: 1.5rem; + min-width: 200px; +} + +td p img { + max-width: unset !important; + border-radius: unset !important; +} + +tbody tr:nth-child(2n-1) { + background-color: #fff; +} +tbody tr:nth-child(2n) { + background-color: #f2f3f5; +} + +table { + border-collapse: separate; + border-spacing: 0; + border-radius: 16px; + overflow: hidden; + border: 1px solid #dbdbdb; +} +table th, +table td { + border: none; + border-right: 1px solid #dbdbdb; + border-bottom: 1px solid #dbdbdb; +} +table th:last-child, +table td:last-child { + border-right: none; +} +table tr:last-child td { + border-bottom: none; +} + +.pswp__top-bar { + height: 100%; +} + +.pswp__counter { + position: absolute; + margin: 0; + left: 50%; + top: 100%; + transform: translate(-50%, -100%) translateY(-44px); +} + +img[id^='ZH-CN_TOPIC'], +p > video { + display: block; + margin: 0 auto; + max-height: 35vh; + max-width: 100%; + text-align: center; + border-radius: 1rem; + overflow: hidden; +} + +img[id^='ZH-CN_TOPIC'] { + background-color: #f7f8fa; +} + +table img[id^='ZH-CN_TOPIC'] { + background-color: transparent; +} + +p:has(video) { + text-align: center; +} + +/* 初始隐藏 .figcap 元素 */ +.figcap { + display: none; + margin-top: 1rem; + text-align: center; + font-size: 0.875rem; + color: #00000099; +} + +.figcap .figurenumber { + display: none; +} + +.show { + display: block; +} + +/* footer */ +.footer { + display: flex; + flex-direction: column; + align-items: center; + margin: 24px auto 76px; + border-radius: 16px; + overflow: hidden; + background-color: #f1f3f5; + background-image: url(../image/f_dwen.png); + background-repeat: repeat; + max-width: 1200px; +} +.footerImg { + max-width: 328px; +} +@media (prefers-color-scheme: dark) { + .footer { + background-image: url(../image/f_dwen_dark.png); + } +} + +/* video */ +video { + max-width: 100%; + border-radius: 16px; +} + +/* viewer */ + +.viewer-canvas { + padding-top: 24px; +} + +.viewer-title { + margin-bottom: 30px; +} + +.viewer-backdrop { + background-color: rgba(0, 0, 0, 0.9); +} + + +/* 一多适配 */ +@media screen and (min-width: 600px) { + img[id^='ZH-CN_TOPIC'], + .footerImg, + p > video { + max-width: calc(1.2 * 328px); + } +} + +@media screen and (min-width: 900px) { + img[id^='ZH-CN_TOPIC'], + .footerImg, + p > video { + max-width: calc(1.5 * 328px); + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/dist/jquery.js b/products/phone/src/main/resources/resfile/articlecols/common/dist/jquery.js new file mode 100644 index 0000000000000000000000000000000000000000..e581cbc66a3b8aee4a46a181d3662256b9a37525 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/dist/jquery.js @@ -0,0 +1,4 @@ +import{c as Rr,g as Ir}from"../../../dist/common2.js";var _n={exports:{}};/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */(function(zn){(function(H,st){zn.exports=H.document?st(H,!0):function(Z){if(!Z.document)throw new Error("jQuery requires a window with a document");return st(Z)}})(typeof window<"u"?window:Rr,function(H,st){var Z=[],Vt=Object.getPrototypeOf,ae=Z.slice,Gt=Z.flat?function(e){return Z.flat.call(e)}:function(e){return Z.concat.apply([],e)},ut=Z.push,be=Z.indexOf,lt={},Yt=lt.toString,Ge=lt.hasOwnProperty,Qt=Ge.toString,Xn=Qt.call(Object),O={},P=function(e){return typeof e=="function"&&typeof e.nodeType!="number"&&typeof e.item!="function"},Pe=function(e){return e!=null&&e===e.window},q=H.document,Un={type:!0,src:!0,nonce:!0,noModule:!0};function Jt(e,t,n){var r,o,a=(n=n||q).createElement("script");if(a.text=e,t)for(r in Un)(o=t[r]||t.getAttribute&&t.getAttribute(r))&&a.setAttribute(r,o);n.head.appendChild(a).parentNode.removeChild(a)}function Me(e){return e==null?e+"":typeof e=="object"||typeof e=="function"?lt[Yt.call(e)]||"object":typeof e}var Kt="3.7.1",Vn=/HTML$/i,i=function(e,t){return new i.fn.init(e,t)};function Ct(e){var t=!!e&&"length"in e&&e.length,n=Me(e);return!P(e)&&!Pe(e)&&(n==="array"||t===0||typeof t=="number"&&0+~]|"+F+")"+F+"*"),$t=new RegExp(F+"|>"),ye=new RegExp(Le),ot=new RegExp("^"+ve+"$"),ke={ID:new RegExp("^#("+ve+")"),CLASS:new RegExp("^\\.("+ve+")"),TAG:new RegExp("^("+ve+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+Le),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+F+"*(even|odd|(([+-]|)(\\d*)n|)"+F+"*(?:([+-]|)"+F+"*(\\d+)|))"+F+"*\\)|)","i"),bool:new RegExp("^(?:"+me+")$","i"),needsContext:new RegExp("^"+F+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+F+"*((?:-\\d)?\\d*)"+F+"*\\)|)(?=[^-]|$)","i")},Ee=/^(?:input|select|textarea|button)$/i,Ue=/^h\d$/i,le=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,J=/[+~]/,G=new RegExp("\\\\[\\da-fA-F]{1,6}"+F+"?|\\\\([^\\r\\n\\f])","g"),te=function(u,f){var h="0x"+u.slice(1)-65536;return f||(h<0?String.fromCharCode(h+65536):String.fromCharCode(h>>10|55296,1023&h|56320))},Se=function(){je()},ne=bt(function(u){return u.disabled===!0&&$(u,"fieldset")},{dir:"parentNode",next:"legend"});try{g.apply(Z=ae.call(we.childNodes),we.childNodes),Z[we.childNodes.length].nodeType}catch{g={apply:function(f,h){kt.apply(f,ae.call(h))},call:function(f){kt.apply(f,ae.call(arguments,1))}}}function S(u,f,h,m){var y,w,T,C,b,W,N,L=f&&f.ownerDocument,R=f?f.nodeType:9;if(h=h||[],typeof u!="string"||!u||R!==1&&R!==9&&R!==11)return h;if(!m&&(je(f),f=f||a,c)){if(R!==11&&(b=le.exec(u)))if(y=b[1]){if(R===9){if(!(T=f.getElementById(y)))return h;if(T.id===y)return g.call(h,T),h}else if(L&&(T=L.getElementById(y))&&S.contains(f,T)&&T.id===y)return g.call(h,T),h}else{if(b[2])return g.apply(h,f.getElementsByTagName(u)),h;if((y=b[3])&&f.getElementsByClassName)return g.apply(h,f.getElementsByClassName(y)),h}if(!(X[u+" "]||l&&l.test(u))){if(N=u,L=f,R===1&&($t.test(u)||it.test(u))){for((L=J.test(u)&&Bt(f.parentNode)||f)==f&&O.scope||((C=f.getAttribute("id"))?C=i.escapeSelector(C):f.setAttribute("id",C=v)),w=(W=at(u)).length;w--;)W[w]=(C?"#"+C:":scope")+" "+xt(W[w]);N=W.join(",")}try{return g.apply(h,L.querySelectorAll(N)),h}catch{X(u,!0)}finally{C===v&&f.removeAttribute("id")}}}return Fn(u.replace(Ye,"$1"),f,h,m)}function U(){var u=[];return function f(h,m){return u.push(h+" ")>t.cacheLength&&delete f[u.shift()],f[h+" "]=m}}function V(u){return u[v]=!0,u}function B(u){var f=a.createElement("fieldset");try{return!!u(f)}catch{return!1}finally{f.parentNode&&f.parentNode.removeChild(f),f=null}}function xe(u){return function(f){return $(f,"input")&&f.type===u}}function Ve(u){return function(f){return($(f,"input")||$(f,"button"))&&f.type===u}}function yt(u){return function(f){return"form"in f?f.parentNode&&f.disabled===!1?"label"in f?"label"in f.parentNode?f.parentNode.disabled===u:f.disabled===u:f.isDisabled===u||f.isDisabled!==!u&&ne(f)===u:f.disabled===u:"label"in f&&f.disabled===u}}function He(u){return V(function(f){return f=+f,V(function(h,m){for(var y,w=u([],h.length,f),T=w.length;T--;)h[y=w[T]]&&(h[y]=!(m[y]=h[y]))})})}function Bt(u){return u&&typeof u.getElementsByTagName<"u"&&u}function je(u){var f,h=u?u.ownerDocument||u:we;return h!=a&&h.nodeType===9&&h.documentElement&&(s=(a=h).documentElement,c=!i.isXMLDoc(a),p=s.matches||s.webkitMatchesSelector||s.msMatchesSelector,s.msMatchesSelector&&we!=a&&(f=a.defaultView)&&f.top!==f&&f.addEventListener("unload",Se),O.getById=B(function(m){return s.appendChild(m).id=i.expando,!a.getElementsByName||!a.getElementsByName(i.expando).length}),O.disconnectedMatch=B(function(m){return p.call(m,"*")}),O.scope=B(function(){return a.querySelectorAll(":scope")}),O.cssHas=B(function(){try{return a.querySelector(":has(*,:jqfake)"),!1}catch{return!0}}),O.getById?(t.filter.ID=function(m){var y=m.replace(G,te);return function(w){return w.getAttribute("id")===y}},t.find.ID=function(m,y){if(typeof y.getElementById<"u"&&c){var w=y.getElementById(m);return w?[w]:[]}}):(t.filter.ID=function(m){var y=m.replace(G,te);return function(w){var T=typeof w.getAttributeNode<"u"&&w.getAttributeNode("id");return T&&T.value===y}},t.find.ID=function(m,y){if(typeof y.getElementById<"u"&&c){var w,T,C,b=y.getElementById(m);if(b){if((w=b.getAttributeNode("id"))&&w.value===m)return[b];for(C=y.getElementsByName(m),T=0;b=C[T++];)if((w=b.getAttributeNode("id"))&&w.value===m)return[b]}return[]}}),t.find.TAG=function(m,y){return typeof y.getElementsByTagName<"u"?y.getElementsByTagName(m):y.querySelectorAll(m)},t.find.CLASS=function(m,y){if(typeof y.getElementsByClassName<"u"&&c)return y.getElementsByClassName(m)},l=[],B(function(m){var y;s.appendChild(m).innerHTML="",m.querySelectorAll("[selected]").length||l.push("\\["+F+"*(?:value|"+me+")"),m.querySelectorAll("[id~="+v+"-]").length||l.push("~="),m.querySelectorAll("a#"+v+"+*").length||l.push(".#.+[+~]"),m.querySelectorAll(":checked").length||l.push(":checked"),(y=a.createElement("input")).setAttribute("type","hidden"),m.appendChild(y).setAttribute("name","D"),s.appendChild(m).disabled=!0,m.querySelectorAll(":disabled").length!==2&&l.push(":enabled",":disabled"),(y=a.createElement("input")).setAttribute("name",""),m.appendChild(y),m.querySelectorAll("[name='']").length||l.push("\\["+F+"*name"+F+"*="+F+`*(?:''|"")`)}),O.cssHas||l.push(":has"),l=l.length&&new RegExp(l.join("|")),Q=function(m,y){if(m===y)return o=!0,0;var w=!m.compareDocumentPosition-!y.compareDocumentPosition;return w||(1&(w=(m.ownerDocument||m)==(y.ownerDocument||y)?m.compareDocumentPosition(y):1)||!O.sortDetached&&y.compareDocumentPosition(m)===w?m===a||m.ownerDocument==we&&S.contains(we,m)?-1:y===a||y.ownerDocument==we&&S.contains(we,y)?1:r?be.call(r,m)-be.call(r,y):0:4&w?-1:1)}),a}for(e in S.matches=function(u,f){return S(u,null,null,f)},S.matchesSelector=function(u,f){if(je(u),c&&!X[f+" "]&&(!l||!l.test(f)))try{var h=p.call(u,f);if(h||O.disconnectedMatch||u.document&&u.document.nodeType!==11)return h}catch{X(f,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(u){return u[1]=u[1].replace(G,te),u[3]=(u[3]||u[4]||u[5]||"").replace(G,te),u[2]==="~="&&(u[3]=" "+u[3]+" "),u.slice(0,4)},CHILD:function(u){return u[1]=u[1].toLowerCase(),u[1].slice(0,3)==="nth"?(u[3]||S.error(u[0]),u[4]=+(u[4]?u[5]+(u[6]||1):2*(u[3]==="even"||u[3]==="odd")),u[5]=+(u[7]+u[8]||u[3]==="odd")):u[3]&&S.error(u[0]),u},PSEUDO:function(u){var f,h=!u[6]&&u[2];return ke.CHILD.test(u[0])?null:(u[3]?u[2]=u[4]||u[5]||"":h&&ye.test(h)&&(f=at(h,!0))&&(f=h.indexOf(")",h.length-f)-h.length)&&(u[0]=u[0].slice(0,f),u[2]=h.slice(0,f)),u.slice(0,3))}},filter:{TAG:function(u){var f=u.replace(G,te).toLowerCase();return u==="*"?function(){return!0}:function(h){return $(h,f)}},CLASS:function(u){var f=k[u+" "];return f||(f=new RegExp("(^|"+F+")"+u+"("+F+"|$)"))&&k(u,function(h){return f.test(typeof h.className=="string"&&h.className||typeof h.getAttribute<"u"&&h.getAttribute("class")||"")})},ATTR:function(u,f,h){return function(m){var y=S.attr(m,u);return y==null?f==="!=":!f||(y+="",f==="="?y===h:f==="!="?y!==h:f==="^="?h&&y.indexOf(h)===0:f==="*="?h&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function Et(e,t,n){return P(t)?i.grep(e,function(r,o){return!!t.call(r,o,r)!==n}):t.nodeType?i.grep(e,function(r){return r===t!==n}):typeof t!="string"?i.grep(e,function(r){return-1)[^>]*|#([\w-]+))$/;(i.fn.init=function(e,t,n){var r,o;if(!e)return this;if(n=n||nn,typeof e=="string"){if(!(r=e[0]==="<"&&e[e.length-1]===">"&&3<=e.length?[null,e,null]:Zn.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof i?t[0]:t,i.merge(this,i.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:q,!0)),tn.test(r[1])&&i.isPlainObject(t))for(r in t)P(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(o=q.getElementById(r[2]))&&(this[0]=o,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):P(e)?n.ready!==void 0?n.ready(e):e(i):i.makeArray(e,this)}).prototype=i.fn,nn=i(q);var er=/^(?:parents|prev(?:Until|All))/,tr={children:!0,contents:!0,next:!0,prev:!0};function rn(e,t){for(;(e=e[t])&&e.nodeType!==1;);return e}i.fn.extend({has:function(e){var t=i(e,this),n=t.length;return this.filter(function(){for(var r=0;r\x20\t\r\n\f]*)/i,fn=/^$|^module$|\/(?:java|ecma)script/i;De=q.createDocumentFragment().appendChild(q.createElement("div")),(dt=q.createElement("input")).setAttribute("type","radio"),dt.setAttribute("checked","checked"),dt.setAttribute("name","t"),De.appendChild(dt),O.checkClone=De.cloneNode(!0).cloneNode(!0).lastChild.checked,De.innerHTML="",O.noCloneChecked=!!De.cloneNode(!0).lastChild.defaultValue,De.innerHTML="",O.option=!!De.lastChild;var ue={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ie(e,t){var n;return n=typeof e.getElementsByTagName<"u"?e.getElementsByTagName(t||"*"):typeof e.querySelectorAll<"u"?e.querySelectorAll(t||"*"):[],t===void 0||t&&$(e,t)?i.merge([e],n):n}function jt(e,t){for(var n=0,r=e.length;n",""]);var lr=/<|&#?\w+;/;function pn(e,t,n,r,o){for(var a,s,c,l,p,g,v=t.createDocumentFragment(),d=[],x=0,k=e.length;x\s*$/g;function hn(e,t){return $(e,"table")&&$(t.nodeType!==11?t:t.firstChild,"tr")&&i(e).children("tbody")[0]||e}function dr(e){return e.type=(e.getAttribute("type")!==null)+"/"+e.type,e}function hr(e){return(e.type||"").slice(0,5)==="true/"?e.type=e.type.slice(5):e.removeAttribute("type"),e}function gn(e,t){var n,r,o,a,s,c;if(t.nodeType===1){if(E.hasData(e)&&(c=E.get(e).events))for(o in E.remove(t,"handle events"),c)for(n=0,r=c[o].length;n"u"?i.prop(e,t,n):(a===1&&i.isXMLDoc(e)||(o=i.attrHooks[t.toLowerCase()]||(i.expr.match.bool.test(t)?An:void 0)),n!==void 0?n===null?void i.removeAttr(e,t):o&&"set"in o&&(r=o.set(e,n,t))!==void 0?r:(e.setAttribute(t,n+""),n):o&&"get"in o&&(r=o.get(e,t))!==null?r:(r=i.find.attr(e,t))==null?void 0:r)},attrHooks:{type:{set:function(e,t){if(!O.radioValue&&t==="radio"&&$(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,o=t&&t.match(he);if(o&&e.nodeType===1)for(;n=o[r++];)e.removeAttribute(n)}}),An={set:function(e,t,n){return t===!1?i.removeAttr(e,n):e.setAttribute(n,n),n}},i.each(i.expr.match.bool.source.match(/\w+/g),function(e,t){var n=tt[t]||i.find.attr;tt[t]=function(r,o,a){var s,c,l=o.toLowerCase();return a||(c=tt[l],tt[l]=s,s=n(r,o,a)!=null?l:null,tt[l]=c),s}});var br=/^(?:input|select|textarea|button)$/i,wr=/^(?:a|area)$/i;function Ne(e){return(e.match(he)||[]).join(" ")}function qe(e){return e.getAttribute&&e.getAttribute("class")||""}function Ot(e){return Array.isArray(e)?e:typeof e=="string"&&e.match(he)||[]}i.fn.extend({prop:function(e,t){return Te(this,i.prop,e,t,1").attr(e.scriptAttrs||{}).prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(a){t.remove(),n=null,a&&o(a.type==="error"?404:200,a.type)}),q.head.appendChild(t[0])},abort:function(){n&&n()}}});var Rn,In=[],Ft=/(=)\?(?=&|$)|\?\?/;i.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=In.pop()||i.expando+"_"+Dn.guid++;return this[e]=!0,e}}),i.ajaxPrefilter("json jsonp",function(e,t,n){var r,o,a,s=e.jsonp!==!1&&(Ft.test(e.url)?"url":typeof e.data=="string"&&(e.contentType||"").indexOf("application/x-www-form-urlencoded")===0&&Ft.test(e.data)&&"data");if(s||e.dataTypes[0]==="jsonp")return r=e.jsonpCallback=P(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,s?e[s]=e[s].replace(Ft,"$1"+r):e.jsonp!==!1&&(e.url+=(Pt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return a||i.error(r+" was not called"),a[0]},e.dataTypes[0]="json",o=H[r],H[r]=function(){a=arguments},n.always(function(){o===void 0?i(H).removeProp(r):H[r]=o,e[r]&&(e.jsonpCallback=t.jsonpCallback,In.push(r)),a&&P(o)&&o(a[0]),a=o=void 0}),"script"}),O.createHTMLDocument=((Rn=q.implementation.createHTMLDocument("").body).innerHTML="
",Rn.childNodes.length===2),i.parseHTML=function(e,t,n){return typeof e!="string"?[]:(typeof t=="boolean"&&(n=t,t=!1),t||(O.createHTMLDocument?((r=(t=q.implementation.createHTMLDocument("")).createElement("base")).href=q.location.href,t.head.appendChild(r)):t=q),a=!n&&[],(o=tn.exec(e))?[t.createElement(o[1])]:(o=pn([e],t,a),a&&a.length&&i(a).remove(),i.merge([],o.childNodes)));var r,o,a},i.fn.load=function(e,t,n){var r,o,a,s=this,c=e.indexOf(" ");return-1").append(i.parseHTML(l)).find(r):l)}).always(n&&function(l,p){s.each(function(){n.apply(this,a||[l.responseText,p,l])})}),this},i.expr.pseudos.animated=function(e){return i.grep(i.timers,function(t){return e===t.elem}).length},i.offset={setOffset:function(e,t,n){var r,o,a,s,c,l,p=i.css(e,"position"),g=i(e),v={};p==="static"&&(e.style.position="relative"),c=g.offset(),a=i.css(e,"top"),l=i.css(e,"left"),(p==="absolute"||p==="fixed")&&-1<(a+l).indexOf("auto")?(s=(r=g.position()).top,o=r.left):(s=parseFloat(a)||0,o=parseFloat(l)||0),P(t)&&(t=t.call(e,n,i.extend({},c))),t.top!=null&&(v.top=t.top-c.top+s),t.left!=null&&(v.left=t.left-c.left+o),"using"in t?t.using.call(e,v):g.css(v)}},i.fn.extend({offset:function(e){if(arguments.length)return e===void 0?this:this.each(function(o){i.offset.setOffset(this,e,o)});var t,n,r=this[0];return r?r.getClientRects().length?(t=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],o={top:0,left:0};if(i.css(r,"position")==="fixed")t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&i.css(e,"position")==="static";)e=e.parentNode;e&&e!==r&&e.nodeType===1&&((o=i(e).offset()).top+=i.css(e,"borderTopWidth",!0),o.left+=i.css(e,"borderLeftWidth",!0))}return{top:t.top-o.top-i.css(r,"marginTop",!0),left:t.left-o.left-i.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&i.css(e,"position")==="static";)e=e.offsetParent;return e||Ae})}}),i.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,t){var n=t==="pageYOffset";i.fn[e]=function(r){return Te(this,function(o,a,s){var c;if(Pe(o)?c=o:o.nodeType===9&&(c=o.defaultView),s===void 0)return c?c[t]:o[a];c?c.scrollTo(n?c.pageXOffset:s,n?s:c.pageYOffset):o[a]=s},e,r,arguments.length)}}),i.each(["top","left"],function(e,t){i.cssHooks[t]=yn(O.pixelPosition,function(n,r){if(r)return r=et(n,t),Dt.test(r)?i(n).position()[t]+"px":r})}),i.each({Height:"height",Width:"width"},function(e,t){i.each({padding:"inner"+e,content:t,"":"outer"+e},function(n,r){i.fn[r]=function(o,a){var s=arguments.length&&(n||typeof o!="boolean"),c=n||(o===!0||a===!0?"margin":"border");return Te(this,function(l,p,g){var v;return Pe(l)?r.indexOf("outer")===0?l["inner"+e]:l.document.documentElement["client"+e]:l.nodeType===9?(v=l.documentElement,Math.max(l.body["scroll"+e],v["scroll"+e],l.body["offset"+e],v["offset"+e],v["client"+e])):g===void 0?i.css(l,p,c):i.style(l,p,g,c)},t,s?o:void 0,s)}})}),i.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){i.fn[t]=function(n){return this.on(t,n)}}),i.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return arguments.length===1?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),i.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,t){i.fn[t]=function(n,r){return 0"u"&&(H.jQuery=H.$=i),i})})(_n);var Wr=_n.exports;const Fr=Ir(Wr);window.$=Fr; diff --git a/products/phone/src/main/resources/resfile/articlecols/common/dist/main.css b/products/phone/src/main/resources/resfile/articlecols/common/dist/main.css new file mode 100644 index 0000000000000000000000000000000000000000..de0b96261322d804c280886e12bb557d93ffa158 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/dist/main.css @@ -0,0 +1,9 @@ +pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#383a42;background:#fafafa}.hljs-comment,.hljs-quote{color:#a0a1a7;font-style:italic}.hljs-doctag,.hljs-keyword,.hljs-formula{color:#a626a4}.hljs-section,.hljs-name,.hljs-selector-tag,.hljs-deletion,.hljs-subst{color:#e45649}.hljs-literal{color:#0184bb}.hljs-string,.hljs-regexp,.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string{color:#50a14f}.hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-type,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-number{color:#986801}.hljs-symbol,.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-title{color:#4078f2}.hljs-built_in,.hljs-title.class_,.hljs-class .hljs-title{color:#c18401}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}/*! +* Viewer.js v1.11.3 +* https://fengyuanchen.github.io/viewerjs +* +* Copyright 2015-present Chen Fengyuan +* Released under the MIT license +* +* Date: 2023-03-05T07:01:15.525Z +*/.viewer-close:before,.viewer-flip-horizontal:before,.viewer-flip-vertical:before,.viewer-fullscreen-exit:before,.viewer-fullscreen:before,.viewer-next:before,.viewer-one-to-one:before,.viewer-play:before,.viewer-prev:before,.viewer-reset:before,.viewer-rotate-left:before,.viewer-rotate-right:before,.viewer-zoom-in:before,.viewer-zoom-out:before{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAAUCAYAAABWOyJDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAQPSURBVHic7Zs/iFxVFMa/0U2UaJGksUgnIVhYxVhpjDbZCBmLdAYECxsRFBTUamcXUiSNncgKQbSxsxH8gzAP3FU2jY0kKKJNiiiIghFlccnP4p3nPCdv3p9778vsLOcHB2bfveeb7955c3jvvNkBIMdxnD64a94GHMfZu3iBcRynN7zAOI7TG15gHCeeNUkr8zaxG2lbYDYsdgMbktBsP03jdQwljSXdtBhLOmtjowC9Mg9L+knSlcD8TNKpSA9lBpK2JF2VdDSR5n5J64m0qli399hNFMUlpshQii5jbXTbHGviB0nLNeNDSd9VO4A2UdB2fp+x0eCnaXxWXGA2X0au/3HgN9P4LFCjIANOJdrLr0zzZ+BEpNYDwKbpnQMeAw4m8HjQtM6Z9qa917zPQwFr3M5KgA6J5rTJCdFZJj9/lyvGhsDvwFNVuV2MhhjrK6b9bFiE+j1r87eBl4HDwCF7/U/k+ofAX5b/EXBv5JoLMuILzf3Ap6Z3EzgdqHMCuF7hcQf4HDgeoHnccncqdK/TvSDWffFXI/exICY/xZyqc6XLWF1UFZna4gJ7q8BsRvgd2/xXpo6P+D9dfT7PpECtA3cnWPM0GXGFZh/wgWltA+cDNC7X+AP4GzjZQe+k5dRxuYPeiuXU7e1qwLpDz7dFjXKRaSwuMLvAlG8zZlG+YmiK1HoFqT7wP2z+4Q45TfEGcMt01xLoNZEBTwRqD4BLpnMLeC1A41UmVxsXgXeBayV/Wx20rpTyrpnWRft7p6O/FdqzGrDukPNtkaMoMo3FBdBSQMOnYBCReyf05s126fU9ytfX98+mY54Kxnp7S9K3kj6U9KYdG0h6UdLbkh7poFXMfUnSOyVvL0h6VtIXHbS6nOP+s/Zm9mvyXW1uuC9ohZ72E9uDmXWLJOB1GxsH+DxPftsB8B6wlGDN02TAkxG6+4D3TWsbeC5CS8CDFce+AW500LhhOW2020TRjK3b21HEmgti9m0RonxbdMZeVzV+/4tF3cBpP7E9mKHNL5q8h5g0eYsCMQz0epq8gQrwMXAgcs0FGXGFRcB9wCemF9PkbYqM/Bas7fxLwNeJPdTdpo4itQti8lPMqTpXuozVRVXPpbHI3KkNTB1NfkL81j2mvhDp91HgV9MKuRIqrykj3WPq4rHyL+axj8/qGPmTqi6F9YDlHOvJU6oYcTsh/TYSzWmTE6JT19CtLTJt32D6CmHe0eQn1O8z5AXgT4sx4Vcu0/EQecMydB8z0hUWkTd2t4CrwNEePqMBcAR4mrBbwyXLPWJa8zrXmmLEhNBmfpkuY2102xxrih+pb+ieAb6vGhuA97UcJ5KR8gZ77K+99xxeYBzH6Q3/Z0fHcXrDC4zjOL3hBcZxnN74F+zlvXFWXF9PAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-size:280px;color:transparent;display:block;font-size:0;height:20px;line-height:0;width:20px}.viewer-zoom-in:before{background-position:0 0;content:"Zoom In"}.viewer-zoom-out:before{background-position:-20px 0;content:"Zoom Out"}.viewer-one-to-one:before{background-position:-40px 0;content:"One to One"}.viewer-reset:before{background-position:-60px 0;content:"Reset"}.viewer-prev:before{background-position:-80px 0;content:"Previous"}.viewer-play:before{background-position:-100px 0;content:"Play"}.viewer-next:before{background-position:-120px 0;content:"Next"}.viewer-rotate-left:before{background-position:-140px 0;content:"Rotate Left"}.viewer-rotate-right:before{background-position:-160px 0;content:"Rotate Right"}.viewer-flip-horizontal:before{background-position:-180px 0;content:"Flip Horizontal"}.viewer-flip-vertical:before{background-position:-200px 0;content:"Flip Vertical"}.viewer-fullscreen:before{background-position:-220px 0;content:"Enter Full Screen"}.viewer-fullscreen-exit:before{background-position:-240px 0;content:"Exit Full Screen"}.viewer-close:before{background-position:-260px 0;content:"Close"}.viewer-container{-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none;bottom:0;direction:ltr;font-size:0;left:0;line-height:0;overflow:hidden;position:absolute;right:0;top:0;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.viewer-container ::-moz-selection,.viewer-container::-moz-selection{background-color:transparent}.viewer-container ::selection,.viewer-container::selection{background-color:transparent}.viewer-container:focus{outline:0}.viewer-container img{display:block;height:auto;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.viewer-canvas{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:0}.viewer-canvas>img{height:auto;margin:15px auto;max-width:90%!important;width:auto}.viewer-footer{bottom:0;left:0;overflow:hidden;position:absolute;right:0;text-align:center}.viewer-navbar{background-color:#00000080;overflow:hidden}.viewer-list{box-sizing:content-box;height:50px;margin:0;overflow:hidden;padding:1px 0}.viewer-list>li{color:transparent;cursor:pointer;float:left;font-size:0;height:50px;line-height:0;opacity:.5;overflow:hidden;transition:opacity .15s;width:30px}.viewer-list>li:focus,.viewer-list>li:hover{opacity:.75}.viewer-list>li:focus{outline:0}.viewer-list>li+li{margin-left:1px}.viewer-list>.viewer-loading{position:relative}.viewer-list>.viewer-loading:after{border-width:2px;height:20px;margin-left:-10px;margin-top:-10px;width:20px}.viewer-list>.viewer-active,.viewer-list>.viewer-active:focus,.viewer-list>.viewer-active:hover{opacity:1}.viewer-player{background-color:#000;bottom:0;cursor:none;display:none;right:0;z-index:1}.viewer-player,.viewer-player>img{left:0;position:absolute;top:0}.viewer-toolbar>ul{display:inline-block;margin:0 auto 5px;overflow:hidden;padding:6px 3px}.viewer-toolbar>ul>li{background-color:#00000080;border-radius:50%;cursor:pointer;float:left;height:24px;overflow:hidden;transition:background-color .15s;width:24px}.viewer-toolbar>ul>li:focus,.viewer-toolbar>ul>li:hover{background-color:#000c}.viewer-toolbar>ul>li:focus{box-shadow:0 0 3px #fff;outline:0;position:relative;z-index:1}.viewer-toolbar>ul>li:before{margin:2px}.viewer-toolbar>ul>li+li{margin-left:1px}.viewer-toolbar>ul>.viewer-small{height:18px;margin-bottom:3px;margin-top:3px;width:18px}.viewer-toolbar>ul>.viewer-small:before{margin:-1px}.viewer-toolbar>ul>.viewer-large{height:30px;margin-bottom:-3px;margin-top:-3px;width:30px}.viewer-toolbar>ul>.viewer-large:before{margin:5px}.viewer-tooltip{background-color:#000c;border-radius:10px;color:#fff;display:none;font-size:12px;height:20px;left:50%;line-height:20px;margin-left:-25px;margin-top:-10px;position:absolute;text-align:center;top:50%;width:50px}.viewer-title{color:#ccc;display:inline-block;font-size:12px;line-height:1.2;margin:5px 5%;max-width:90%;min-height:14px;opacity:.8;overflow:hidden;text-overflow:ellipsis;transition:opacity .15s;white-space:nowrap}.viewer-title:hover{opacity:1}.viewer-button{-webkit-app-region:no-drag;background-color:#00000080;border-radius:50%;cursor:pointer;height:80px;overflow:hidden;position:absolute;right:-40px;top:-40px;transition:background-color .15s;width:80px}.viewer-button:focus,.viewer-button:hover{background-color:#000c}.viewer-button:focus{box-shadow:0 0 3px #fff;outline:0}.viewer-button:before{bottom:15px;left:15px;position:absolute}.viewer-fixed{position:fixed}.viewer-open{overflow:hidden}.viewer-show{display:block}.viewer-hide{display:none}.viewer-backdrop{background-color:#00000080}.viewer-invisible{visibility:hidden}.viewer-move{cursor:move;cursor:grab}.viewer-fade{opacity:0}.viewer-in{opacity:1}.viewer-transition{transition:all .3s}@keyframes viewer-spinner{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.viewer-loading:after{animation:viewer-spinner 1s linear infinite;border:4px solid hsla(0,0%,100%,.1);border-left-color:#ffffff80;border-radius:50%;content:"";display:inline-block;height:40px;left:50%;margin-left:-20px;margin-top:-20px;position:absolute;top:50%;width:40px;z-index:1}@media (max-width:767px){.viewer-hide-xs-down{display:none}}@media (max-width:991px){.viewer-hide-sm-down{display:none}}@media (max-width:1199px){.viewer-hide-md-down{display:none}} diff --git a/products/phone/src/main/resources/resfile/articlecols/common/dist/main.js b/products/phone/src/main/resources/resfile/articlecols/common/dist/main.js new file mode 100644 index 0000000000000000000000000000000000000000..86a17b45830811f081c2161c07490a7fb179ec7b --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/dist/main.js @@ -0,0 +1 @@ +import{H as Qt}from"../../../dist/common.js";import"../../../dist/common2.js";const St={backdrop:!0,button:!0,navbar:!0,title:!0,toolbar:!0,className:"",container:"body",filter:null,fullscreen:!0,inheritedAttributes:["crossOrigin","decoding","isMap","loading","referrerPolicy","sizes","srcset","useMap"],initialCoverage:.9,initialViewIndex:0,inline:!1,interval:5e3,keyboard:!0,focus:!0,loading:!0,loop:!0,minWidth:200,minHeight:100,movable:!0,rotatable:!0,scalable:!0,zoomable:!0,zoomOnTouch:!0,zoomOnWheel:!0,slideOnTouch:!0,toggleOnDblclick:!0,tooltip:!0,transition:!0,zIndex:2015,zIndexInline:0,zoomRatio:.1,minZoomRatio:.01,maxZoomRatio:100,url:"src",ready:null,show:null,shown:null,hide:null,hidden:null,view:null,viewed:null,move:null,moved:null,rotate:null,rotated:null,scale:null,scaled:null,zoom:null,zoomed:null,play:null,stop:null},te='
',ht=typeof window<"u"&&typeof window.document<"u",M=ht?window:{},Y=ht&&M.document.documentElement?"ontouchstart"in M.document.documentElement:!1,bt=ht?"PointerEvent"in M:!1,f="viewer",ot="move",Kt="switch",K="zoom",et=`${f}-active`,ee=`${f}-close`,at=`${f}-fade`,mt=`${f}-fixed`,ie=`${f}-fullscreen`,It=`${f}-fullscreen-exit`,F=`${f}-hide`,se=`${f}-hide-md-down`,ne=`${f}-hide-sm-down`,oe=`${f}-hide-xs-down`,D=`${f}-in`,G=`${f}-invisible`,U=`${f}-loading`,ae=`${f}-move`,Nt=`${f}-open`,H=`${f}-show`,v=`${f}-transition`,Z="click",gt="dblclick",At="dragstart",Ct="focusin",xt="keydown",k="load",W="error",re=Y?"touchend touchcancel":"mouseup",le=Y?"touchmove":"mousemove",he=Y?"touchstart":"mousedown",Dt=bt?"pointerdown":he,kt=bt?"pointermove":le,Lt=bt?"pointerup pointercancel":re,zt="resize",z="transitionend",Ot="wheel",Rt="ready",_t="show",Mt="shown",Vt="hide",$t="hidden",Ft="view",J="viewed",Wt="move",Ht="moved",Pt="rotate",qt="rotated",Xt="scale",Bt="scaled",Yt="zoom",Ut="zoomed",Zt="play",jt="stop",lt=`${f}Action`,wt=/\s\s*/,it=["zoom-in","zoom-out","one-to-one","reset","prev","play","next","rotate-left","rotate-right","flip-horizontal","flip-vertical"];function Q(t){return typeof t=="string"}const ce=Number.isNaN||M.isNaN;function y(t){return typeof t=="number"&&!ce(t)}function q(t){return typeof t>"u"}function j(t){return typeof t=="object"&&t!==null}const{hasOwnProperty:de}=Object.prototype;function X(t){if(!j(t))return!1;try{const{constructor:e}=t,{prototype:i}=e;return e&&i&&de.call(i,"isPrototypeOf")}catch{return!1}}function p(t){return typeof t=="function"}function w(t,e){if(t&&p(e))if(Array.isArray(t)||y(t.length)){const{length:i}=t;let s;for(s=0;s{e.call(t,t[i],i,t)});return t}const C=Object.assign||function(e,...i){return j(e)&&i.length>0&&i.forEach(s=>{j(s)&&Object.keys(s).forEach(n=>{e[n]=s[n]})}),e},ue=/^(?:width|height|left|top|marginLeft|marginTop)$/;function O(t,e){const{style:i}=t;w(e,(s,n)=>{ue.test(n)&&y(s)&&(s+="px"),i[n]=s})}function fe(t){return Q(t)?t.replace(/&(?!amp;|quot;|#39;|lt;|gt;)/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">"):t}function P(t,e){return!t||!e?!1:t.classList?t.classList.contains(e):t.className.indexOf(e)>-1}function c(t,e){if(!t||!e)return;if(y(t.length)){w(t,s=>{c(s,e)});return}if(t.classList){t.classList.add(e);return}const i=t.className.trim();i?i.indexOf(e)<0&&(t.className=`${i} ${e}`):t.className=e}function g(t,e){if(!(!t||!e)){if(y(t.length)){w(t,i=>{g(i,e)});return}if(t.classList){t.classList.remove(e);return}t.className.indexOf(e)>=0&&(t.className=t.className.replace(e,""))}}function tt(t,e,i){if(e){if(y(t.length)){w(t,s=>{tt(s,e,i)});return}i?c(t,e):g(t,e)}}const me=/([a-z\d])([A-Z])/g;function Et(t){return t.replace(me,"$1-$2").toLowerCase()}function B(t,e){return j(t[e])?t[e]:t.dataset?t.dataset[e]:t.getAttribute(`data-${Et(e)}`)}function pt(t,e,i){j(i)?t[e]=i:t.dataset?t.dataset[e]=i:t.setAttribute(`data-${Et(e)}`,i)}const Gt=(()=>{let t=!1;if(ht){let e=!1;const i=()=>{},s=Object.defineProperty({},"once",{get(){return t=!0,e},set(n){e=n}});M.addEventListener("test",i,s),M.removeEventListener("test",i,s)}return t})();function b(t,e,i,s={}){let n=i;e.trim().split(wt).forEach(o=>{if(!Gt){const{listeners:a}=t;a&&a[o]&&a[o][i]&&(n=a[o][i],delete a[o][i],Object.keys(a[o]).length===0&&delete a[o],Object.keys(a).length===0&&delete t.listeners)}t.removeEventListener(o,n,s)})}function d(t,e,i,s={}){let n=i;e.trim().split(wt).forEach(o=>{if(s.once&&!Gt){const{listeners:a={}}=t;n=(...r)=>{delete a[o][i],t.removeEventListener(o,n,s),i.apply(t,r)},a[o]||(a[o]={}),a[o][i]&&t.removeEventListener(o,a[o][i],s),a[o][i]=n,t.listeners=a}t.addEventListener(o,n,s)})}function T(t,e,i,s){let n;return p(Event)&&p(CustomEvent)?n=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:i,...s}):(n=document.createEvent("CustomEvent"),n.initCustomEvent(e,!0,!0,i)),t.dispatchEvent(n)}function ge(t){const e=t.getBoundingClientRect();return{left:e.left+(window.pageXOffset-document.documentElement.clientLeft),top:e.top+(window.pageYOffset-document.documentElement.clientTop)}}function rt({rotate:t,scaleX:e,scaleY:i,translateX:s,translateY:n}){const o=[];y(s)&&s!==0&&o.push(`translateX(${s}px)`),y(n)&&n!==0&&o.push(`translateY(${n}px)`),y(t)&&t!==0&&o.push(`rotate(${t}deg)`),y(e)&&e!==1&&o.push(`scaleX(${e})`),y(i)&&i!==1&&o.push(`scaleY(${i})`);const a=o.length?o.join(" "):"none";return{WebkitTransform:a,msTransform:a,transform:a}}function pe(t){return Q(t)?decodeURIComponent(t.replace(/^.*\//,"").replace(/[?&#].*$/,"")):""}const ft=M.navigator&&/(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(M.navigator.userAgent);function Jt(t,e,i){const s=document.createElement("img");if(t.naturalWidth&&!ft)return i(t.naturalWidth,t.naturalHeight),s;const n=document.body||document.documentElement;return s.onload=()=>{i(s.width,s.height),ft||n.removeChild(s)},w(e.inheritedAttributes,o=>{const a=t.getAttribute(o);a!==null&&s.setAttribute(o,a)}),s.src=t.src,ft||(s.style.cssText="left:0;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;opacity:0;position:absolute;top:0;z-index:-1;",n.appendChild(s)),s}function st(t){switch(t){case 2:return oe;case 3:return ne;case 4:return se;default:return""}}function be(t){const e={...t},i=[];return w(t,(s,n)=>{delete e[n],w(e,o=>{const a=Math.abs(s.startX-o.startX),r=Math.abs(s.startY-o.startY),l=Math.abs(s.endX-o.endX),h=Math.abs(s.endY-o.endY),m=Math.sqrt(a*a+r*r),E=(Math.sqrt(l*l+h*h)-m)/m;i.push(E)})}),i.sort((s,n)=>Math.abs(s){e+=n,i+=o,s+=1}),e/=s,i/=s,{pageX:e,pageY:i}}const Ee={render(){this.initContainer(),this.initViewer(),this.initList(),this.renderViewer()},initBody(){const{ownerDocument:t}=this.element,e=t.body||t.documentElement;this.body=e,this.scrollbarWidth=window.innerWidth-t.documentElement.clientWidth,this.initialBodyPaddingRight=e.style.paddingRight,this.initialBodyComputedPaddingRight=window.getComputedStyle(e).paddingRight},initContainer(){this.containerData={width:window.innerWidth,height:window.innerHeight}},initViewer(){const{options:t,parent:e}=this;let i;t.inline&&(i={width:Math.max(e.offsetWidth,t.minWidth),height:Math.max(e.offsetHeight,t.minHeight)},this.parentData=i),(this.fulled||!i)&&(i=this.containerData),this.viewerData=C({},i)},renderViewer(){this.options.inline&&!this.fulled&&O(this.viewer,this.viewerData)},initList(){const{element:t,options:e,list:i}=this,s=[];i.innerHTML="",w(this.images,(n,o)=>{const{src:a}=n,r=n.alt||pe(a),l=this.getImageURL(n);if(a||l){const h=document.createElement("li"),m=document.createElement("img");w(e.inheritedAttributes,u=>{const E=n.getAttribute(u);E!==null&&m.setAttribute(u,E)}),e.navbar&&(m.src=a||l),m.alt=r,m.setAttribute("data-original-url",l||a),h.setAttribute("data-index",o),h.setAttribute("data-viewer-action","view"),h.setAttribute("role","button"),e.keyboard&&h.setAttribute("tabindex",0),h.appendChild(m),i.appendChild(h),s.push(h)}}),this.items=s,w(s,n=>{const o=n.firstElementChild;let a,r;pt(o,"filled",!0),e.loading&&c(n,U),d(o,k,a=l=>{b(o,W,r),e.loading&&g(n,U),this.loadImage(l)},{once:!0}),d(o,W,r=()=>{b(o,k,a),e.loading&&g(n,U)},{once:!0})}),e.transition&&d(t,J,()=>{c(i,v)},{once:!0})},renderList(){const{index:t}=this,e=this.items[t];if(!e)return;const i=e.nextElementSibling,s=parseInt(window.getComputedStyle(i||e).marginLeft,10),{offsetWidth:n}=e,o=n+s;O(this.list,C({width:o*this.length-s},rt({translateX:(this.viewerData.width-n)/2-o*t})))},resetList(){const{list:t}=this;t.innerHTML="",g(t,v),O(t,rt({translateX:0}))},initImage(t){const{options:e,image:i,viewerData:s}=this,n=this.footer.offsetHeight,o=s.width,a=Math.max(s.height-n,n),r=this.imageData||{};let l;this.imageInitializing={abort:()=>{l.onload=null}},l=Jt(i,e,(h,m)=>{const u=h/m;let E=Math.max(0,Math.min(1,e.initialCoverage)),N=o,I=a;this.imageInitializing=!1,a*u>o?I=o/u:N=a*u,E=y(E)?E:.9,N=Math.min(N*E,h),I=Math.min(I*E,m);const A=(o-N)/2,S=(a-I)/2,L={left:A,top:S,x:A,y:S,width:N,height:I,oldRatio:1,ratio:N/h,aspectRatio:u,naturalWidth:h,naturalHeight:m},x=C({},L);e.rotatable&&(L.rotate=r.rotate||0,x.rotate=0),e.scalable&&(L.scaleX=r.scaleX||1,L.scaleY=r.scaleY||1,x.scaleX=1,x.scaleY=1),this.imageData=L,this.initialImageData=x,t&&t()})},renderImage(t){const{image:e,imageData:i}=this;if(O(e,C({width:i.width,height:i.height,marginLeft:i.x,marginTop:i.y},rt(i))),t)if((this.viewing||this.moving||this.rotating||this.scaling||this.zooming)&&this.options.transition&&P(e,v)){const s=()=>{this.imageRendering=!1,t()};this.imageRendering={abort:()=>{b(e,z,s)}},d(e,z,s,{once:!0})}else t()},resetImage(){if(this.viewing||this.viewed){const{image:t}=this;this.viewing&&this.viewing.abort(),t.parentNode.removeChild(t),this.image=null}}},ye={bind(){const{options:t,viewer:e,canvas:i}=this,s=this.element.ownerDocument;d(e,Z,this.onClick=this.click.bind(this)),d(e,At,this.onDragStart=this.dragstart.bind(this)),d(i,Dt,this.onPointerDown=this.pointerdown.bind(this)),d(s,kt,this.onPointerMove=this.pointermove.bind(this)),d(s,Lt,this.onPointerUp=this.pointerup.bind(this)),d(s,xt,this.onKeyDown=this.keydown.bind(this)),d(window,zt,this.onResize=this.resize.bind(this)),t.zoomable&&t.zoomOnWheel&&d(e,Ot,this.onWheel=this.wheel.bind(this),{passive:!1,capture:!0}),t.toggleOnDblclick&&d(i,gt,this.onDblclick=this.dblclick.bind(this))},unbind(){const{options:t,viewer:e,canvas:i}=this,s=this.element.ownerDocument;b(e,Z,this.onClick),b(e,At,this.onDragStart),b(i,Dt,this.onPointerDown),b(s,kt,this.onPointerMove),b(s,Lt,this.onPointerUp),b(s,xt,this.onKeyDown),b(window,zt,this.onResize),t.zoomable&&t.zoomOnWheel&&b(e,Ot,this.onWheel,{passive:!1,capture:!0}),t.toggleOnDblclick&&b(i,gt,this.onDblclick)}},ve={click(t){const{options:e,imageData:i}=this;let{target:s}=t,n=B(s,lt);switch(!n&&s.localName==="img"&&s.parentElement.localName==="li"&&(s=s.parentElement,n=B(s,lt)),Y&&t.isTrusted&&s===this.canvas&&clearTimeout(this.clickCanvasTimeout),n){case"mix":this.played?this.stop():e.inline?this.fulled?this.exit():this.full():this.hide();break;case"hide":this.pointerMoved||this.hide();break;case"view":this.view(B(s,"index"));break;case"zoom-in":this.zoom(.1,!0);break;case"zoom-out":this.zoom(-.1,!0);break;case"one-to-one":this.toggle();break;case"reset":this.reset();break;case"prev":this.prev(e.loop);break;case"play":this.play(e.fullscreen);break;case"next":this.next(e.loop);break;case"rotate-left":this.rotate(-90);break;case"rotate-right":this.rotate(90);break;case"flip-horizontal":this.scaleX(-i.scaleX||-1);break;case"flip-vertical":this.scaleY(-i.scaleY||-1);break;default:this.played&&this.stop()}},dblclick(t){t.preventDefault(),this.viewed&&t.target===this.image&&(Y&&t.isTrusted&&clearTimeout(this.doubleClickImageTimeout),this.toggle(t.isTrusted?t:t.detail&&t.detail.originalEvent))},load(){this.timeout&&(clearTimeout(this.timeout),this.timeout=!1);const{element:t,options:e,image:i,index:s,viewerData:n}=this;g(i,G),e.loading&&g(this.canvas,U),i.style.cssText=`height:0;margin-left:${n.width/2}px;margin-top:${n.height/2}px;max-width:none!important;position:relative;width:0;`,this.initImage(()=>{tt(i,ae,e.movable),tt(i,v,e.transition),this.renderImage(()=>{this.viewed=!0,this.viewing=!1,p(e.viewed)&&d(t,J,e.viewed,{once:!0}),T(t,J,{originalImage:this.images[s],index:s,image:i},{cancelable:!1})})})},loadImage(t){const e=t.target,i=e.parentNode,s=i.offsetWidth||30,n=i.offsetHeight||50,o=!!B(e,"filled");Jt(e,this.options,(a,r)=>{const l=a/r;let h=s,m=n;n*l>s?o?h=n*l:m=s/l:o?m=s/l:h=n*l,O(e,C({width:h,height:m},rt({translateX:(s-h)/2,translateY:(n-m)/2})))})},keydown(t){const{options:e}=this;if(!e.keyboard)return;const i=t.keyCode||t.which||t.charCode;switch(i){case 13:this.viewer.contains(t.target)&&this.click(t);break}if(this.fulled)switch(i){case 27:this.played?this.stop():e.inline?this.fulled&&this.exit():this.hide();break;case 32:this.played&&this.stop();break;case 37:this.played&&this.playing?this.playing.prev():this.prev(e.loop);break;case 38:t.preventDefault(),this.zoom(e.zoomRatio,!0);break;case 39:this.played&&this.playing?this.playing.next():this.next(e.loop);break;case 40:t.preventDefault(),this.zoom(-e.zoomRatio,!0);break;case 48:case 49:t.ctrlKey&&(t.preventDefault(),this.toggle());break}},dragstart(t){t.target.localName==="img"&&t.preventDefault()},pointerdown(t){const{options:e,pointers:i}=this,{buttons:s,button:n}=t;if(this.pointerMoved=!1,!this.viewed||this.showing||this.viewing||this.hiding||(t.type==="mousedown"||t.type==="pointerdown"&&t.pointerType==="mouse")&&(y(s)&&s!==1||y(n)&&n!==0||t.ctrlKey))return;t.preventDefault(),t.changedTouches?w(t.changedTouches,a=>{i[a.identifier]=nt(a)}):i[t.pointerId||0]=nt(t);let o=e.movable?ot:!1;e.zoomOnTouch&&e.zoomable&&Object.keys(i).length>1?o=K:e.slideOnTouch&&(t.pointerType==="touch"||t.type==="touchstart")&&this.isSwitchable()&&(o=Kt),e.transition&&(o===ot||o===K)&&g(this.image,v),this.action=o},pointermove(t){const{pointers:e,action:i}=this;!this.viewed||!i||(t.preventDefault(),t.changedTouches?w(t.changedTouches,s=>{C(e[s.identifier]||{},nt(s,!0))}):C(e[t.pointerId||0]||{},nt(t,!0)),this.change(t))},pointerup(t){const{options:e,action:i,pointers:s}=this;let n;t.changedTouches?w(t.changedTouches,o=>{n=s[o.identifier],delete s[o.identifier]}):(n=s[t.pointerId||0],delete s[t.pointerId||0]),i&&(t.preventDefault(),e.transition&&(i===ot||i===K)&&c(this.image,v),this.action=!1,Y&&i!==K&&n&&Date.now()-n.timeStamp<500&&(clearTimeout(this.clickCanvasTimeout),clearTimeout(this.doubleClickImageTimeout),e.toggleOnDblclick&&this.viewed&&t.target===this.image?this.imageClicked?(this.imageClicked=!1,this.doubleClickImageTimeout=setTimeout(()=>{T(this.image,gt,{originalEvent:t})},50)):(this.imageClicked=!0,this.doubleClickImageTimeout=setTimeout(()=>{this.imageClicked=!1},500)):(this.imageClicked=!1,e.backdrop&&e.backdrop!=="static"&&t.target===this.canvas&&(this.clickCanvasTimeout=setTimeout(()=>{T(this.canvas,Z,{originalEvent:t})},50)))))},resize(){if(!(!this.isShown||this.hiding)&&(this.fulled&&(this.close(),this.initBody(),this.open()),this.initContainer(),this.initViewer(),this.renderViewer(),this.renderList(),this.viewed&&this.initImage(()=>{this.renderImage()}),this.played)){if(this.options.fullscreen&&this.fulled&&!(document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.msFullscreenElement)){this.stop();return}w(this.player.getElementsByTagName("img"),t=>{d(t,k,this.loadImage.bind(this),{once:!0}),T(t,k)})}},wheel(t){if(!this.viewed||(t.preventDefault(),this.wheeling))return;this.wheeling=!0,setTimeout(()=>{this.wheeling=!1},50);const e=Number(this.options.zoomRatio)||.1;let i=1;t.deltaY?i=t.deltaY>0?1:-1:t.wheelDelta?i=-t.wheelDelta/120:t.detail&&(i=t.detail>0?1:-1),this.zoom(-i*e,!0,null,t)}},Te={show(t=!1){const{element:e,options:i}=this;if(i.inline||this.showing||this.isShown||this.showing)return this;if(!this.ready)return this.build(),this.ready&&this.show(t),this;if(p(i.show)&&d(e,_t,i.show,{once:!0}),T(e,_t)===!1||!this.ready)return this;this.hiding&&this.transitioning.abort(),this.showing=!0,this.open();const{viewer:s}=this;if(g(s,F),s.setAttribute("role","dialog"),s.setAttribute("aria-labelledby",this.title.id),s.setAttribute("aria-modal",!0),s.removeAttribute("aria-hidden"),i.transition&&!t){const n=this.shown.bind(this);this.transitioning={abort:()=>{b(s,z,n),g(s,D)}},c(s,v),s.initialOffsetWidth=s.offsetWidth,d(s,z,n,{once:!0}),c(s,D)}else c(s,D),this.shown();return this},hide(t=!1){const{element:e,options:i}=this;if(i.inline||this.hiding||!(this.isShown||this.showing))return this;if(p(i.hide)&&d(e,Vt,i.hide,{once:!0}),T(e,Vt)===!1)return this;this.showing&&this.transitioning.abort(),this.hiding=!0,this.played?this.stop():this.viewing&&this.viewing.abort();const{viewer:s,image:n}=this,o=()=>{g(s,D),this.hidden()};if(i.transition&&!t){const a=l=>{l&&l.target===s&&(b(s,z,a),this.hidden())},r=()=>{P(s,v)?(d(s,z,a),g(s,D)):o()};this.transitioning={abort:()=>{this.viewed&&P(n,v)?b(n,z,r):P(s,v)&&b(s,z,a)}},this.viewed&&P(n,v)?(d(n,z,r,{once:!0}),this.zoomTo(0,!1,null,null,!0)):r()}else o();return this},view(t=this.options.initialViewIndex){if(t=Number(t)||0,this.hiding||this.played||t<0||t>=this.length||this.viewed&&t===this.index)return this;if(!this.isShown)return this.index=t,this.show();this.viewing&&this.viewing.abort();const{element:e,options:i,title:s,canvas:n}=this,o=this.items[t],a=o.querySelector("img"),r=B(a,"originalUrl"),l=a.getAttribute("alt"),h=document.createElement("img");if(w(i.inheritedAttributes,I=>{const A=a.getAttribute(I);A!==null&&h.setAttribute(I,A)}),h.src=r,h.alt=l,p(i.view)&&d(e,Ft,i.view,{once:!0}),T(e,Ft,{originalImage:this.images[t],index:t,image:h})===!1||!this.isShown||this.hiding||this.played)return this;const m=this.items[this.index];m&&(g(m,et),m.removeAttribute("aria-selected")),c(o,et),o.setAttribute("aria-selected",!0),i.focus&&o.focus(),this.image=h,this.viewed=!1,this.index=t,this.imageData={},c(h,G),i.loading&&c(n,U),n.innerHTML="",n.appendChild(h),this.renderList(),s.innerHTML="";const u=()=>{const{imageData:I}=this,A=Array.isArray(i.title)?i.title[1]:i.title;s.innerHTML=fe(p(A)?A.call(this,h,I):`${l} (${I.naturalWidth} × ${I.naturalHeight})`)};let E,N;return d(e,J,u,{once:!0}),this.viewing={abort:()=>{b(e,J,u),h.complete?this.imageRendering?this.imageRendering.abort():this.imageInitializing&&this.imageInitializing.abort():(h.src="",b(h,k,E),this.timeout&&clearTimeout(this.timeout))}},h.complete?this.load():(d(h,k,E=()=>{b(h,W,N),this.load()},{once:!0}),d(h,W,N=()=>{b(h,k,E),this.timeout&&(clearTimeout(this.timeout),this.timeout=!1),g(h,G),i.loading&&g(this.canvas,U)},{once:!0}),this.timeout&&clearTimeout(this.timeout),this.timeout=setTimeout(()=>{g(h,G),this.timeout=!1},1e3)),this},prev(t=!1){let e=this.index-1;return e<0&&(e=t?this.length-1:0),this.view(e),this},next(t=!1){const e=this.length-1;let i=this.index+1;return i>e&&(i=t?0:e),this.view(i),this},move(t,e=t){const{imageData:i}=this;return this.moveTo(q(t)?t:i.x+Number(t),q(e)?e:i.y+Number(e)),this},moveTo(t,e=t,i=null){const{element:s,options:n,imageData:o}=this;if(t=Number(t),e=Number(e),this.viewed&&!this.played&&n.movable){const a=o.x,r=o.y;let l=!1;if(y(t)?l=!0:t=a,y(e)?l=!0:e=r,l){if(p(n.move)&&d(s,Wt,n.move,{once:!0}),T(s,Wt,{x:t,y:e,oldX:a,oldY:r,originalEvent:i})===!1)return this;o.x=t,o.y=e,o.left=t,o.top=e,this.moving=!0,this.renderImage(()=>{this.moving=!1,p(n.moved)&&d(s,Ht,n.moved,{once:!0}),T(s,Ht,{x:t,y:e,oldX:a,oldY:r,originalEvent:i},{cancelable:!1})})}}return this},rotate(t){return this.rotateTo((this.imageData.rotate||0)+Number(t)),this},rotateTo(t){const{element:e,options:i,imageData:s}=this;if(t=Number(t),y(t)&&this.viewed&&!this.played&&i.rotatable){const n=s.rotate;if(p(i.rotate)&&d(e,Pt,i.rotate,{once:!0}),T(e,Pt,{degree:t,oldDegree:n})===!1)return this;s.rotate=t,this.rotating=!0,this.renderImage(()=>{this.rotating=!1,p(i.rotated)&&d(e,qt,i.rotated,{once:!0}),T(e,qt,{degree:t,oldDegree:n},{cancelable:!1})})}return this},scaleX(t){return this.scale(t,this.imageData.scaleY),this},scaleY(t){return this.scale(this.imageData.scaleX,t),this},scale(t,e=t){const{element:i,options:s,imageData:n}=this;if(t=Number(t),e=Number(e),this.viewed&&!this.played&&s.scalable){const o=n.scaleX,a=n.scaleY;let r=!1;if(y(t)?r=!0:t=o,y(e)?r=!0:e=a,r){if(p(s.scale)&&d(i,Xt,s.scale,{once:!0}),T(i,Xt,{scaleX:t,scaleY:e,oldScaleX:o,oldScaleY:a})===!1)return this;n.scaleX=t,n.scaleY=e,this.scaling=!0,this.renderImage(()=>{this.scaling=!1,p(s.scaled)&&d(i,Bt,s.scaled,{once:!0}),T(i,Bt,{scaleX:t,scaleY:e,oldScaleX:o,oldScaleY:a},{cancelable:!1})})}}return this},zoom(t,e=!1,i=null,s=null){const{imageData:n}=this;return t=Number(t),t<0?t=1/(1-t):t=1+t,this.zoomTo(n.width*t/n.naturalWidth,e,i,s),this},zoomTo(t,e=!1,i=null,s=null,n=!1){const{element:o,options:a,pointers:r,imageData:l}=this,{x:h,y:m,width:u,height:E,naturalWidth:N,naturalHeight:I}=l;if(t=Math.max(0,t),y(t)&&this.viewed&&!this.played&&(n||a.zoomable)){if(!n){const V=Math.max(.01,a.minZoomRatio),$=Math.min(100,a.maxZoomRatio);t=Math.min(Math.max(t,V),$)}if(s)switch(s.type){case"wheel":a.zoomRatio>=.055&&t>.95&&t<1.05&&(t=1);break;case"pointermove":case"touchmove":case"mousemove":t>.99&&t<1.01&&(t=1);break}const A=N*t,S=I*t,L=A-u,x=S-E,R=l.ratio;if(p(a.zoom)&&d(o,Yt,a.zoom,{once:!0}),T(o,Yt,{ratio:t,oldRatio:R,originalEvent:s})===!1)return this;if(this.zooming=!0,s){const V=ge(this.viewer),$=r&&Object.keys(r).length>0?we(r):{pageX:s.pageX,pageY:s.pageY};l.x-=L*(($.pageX-V.left-h)/u),l.y-=x*(($.pageY-V.top-m)/E)}else X(i)&&y(i.x)&&y(i.y)?(l.x-=L*((i.x-h)/u),l.y-=x*((i.y-m)/E)):(l.x-=L/2,l.y-=x/2);l.left=l.x,l.top=l.y,l.width=A,l.height=S,l.oldRatio=R,l.ratio=t,this.renderImage(()=>{this.zooming=!1,p(a.zoomed)&&d(o,Ut,a.zoomed,{once:!0}),T(o,Ut,{ratio:t,oldRatio:R,originalEvent:s},{cancelable:!1})}),e&&this.tooltip()}return this},play(t=!1){if(!this.isShown||this.played)return this;const{element:e,options:i}=this;if(p(i.play)&&d(e,Zt,i.play,{once:!0}),T(e,Zt)===!1)return this;const{player:s}=this,n=this.loadImage.bind(this),o=[];let a=0,r=0;if(this.played=!0,this.onLoadWhenPlay=n,t&&this.requestFullscreen(t),c(s,H),w(this.items,(l,h)=>{const m=l.querySelector("img"),u=document.createElement("img");u.src=B(m,"originalUrl"),u.alt=m.getAttribute("alt"),u.referrerPolicy=m.referrerPolicy,a+=1,c(u,at),tt(u,v,i.transition),P(l,et)&&(c(u,D),r=h),o.push(u),d(u,k,n,{once:!0}),s.appendChild(u)}),y(i.interval)&&i.interval>0){const l=()=>{clearTimeout(this.playing.timeout),g(o[r],D),r-=1,r=r>=0?r:a-1,c(o[r],D),this.playing.timeout=setTimeout(l,i.interval)},h=()=>{clearTimeout(this.playing.timeout),g(o[r],D),r+=1,r=r1&&(this.playing={prev:l,next:h,timeout:setTimeout(h,i.interval)})}return this},stop(){if(!this.played)return this;const{element:t,options:e}=this;if(p(e.stop)&&d(t,jt,e.stop,{once:!0}),T(t,jt)===!1)return this;const{player:i}=this;return clearTimeout(this.playing.timeout),this.playing=!1,this.played=!1,w(i.getElementsByTagName("img"),s=>{b(s,k,this.onLoadWhenPlay)}),g(i,H),i.innerHTML="",this.exitFullscreen(),this},full(){const{options:t,viewer:e,image:i,list:s}=this;return!this.isShown||this.played||this.fulled||!t.inline?this:(this.fulled=!0,this.open(),c(this.button,It),t.transition&&(g(s,v),this.viewed&&g(i,v)),c(e,mt),e.setAttribute("role","dialog"),e.setAttribute("aria-labelledby",this.title.id),e.setAttribute("aria-modal",!0),e.removeAttribute("style"),O(e,{zIndex:t.zIndex}),t.focus&&this.enforceFocus(),this.initContainer(),this.viewerData=C({},this.containerData),this.renderList(),this.viewed&&this.initImage(()=>{this.renderImage(()=>{t.transition&&setTimeout(()=>{c(i,v),c(s,v)},0)})}),this)},exit(){const{options:t,viewer:e,image:i,list:s}=this;return!this.isShown||this.played||!this.fulled||!t.inline?this:(this.fulled=!1,this.close(),g(this.button,It),t.transition&&(g(s,v),this.viewed&&g(i,v)),t.focus&&this.clearEnforceFocus(),e.removeAttribute("role"),e.removeAttribute("aria-labelledby"),e.removeAttribute("aria-modal"),g(e,mt),O(e,{zIndex:t.zIndexInline}),this.viewerData=C({},this.parentData),this.renderViewer(),this.renderList(),this.viewed&&this.initImage(()=>{this.renderImage(()=>{t.transition&&setTimeout(()=>{c(i,v),c(s,v)},0)})}),this)},tooltip(){const{options:t,tooltipBox:e,imageData:i}=this;return!this.viewed||this.played||!t.tooltip?this:(e.textContent=`${Math.round(i.ratio*100)}%`,this.tooltipping?clearTimeout(this.tooltipping):t.transition?(this.fading&&T(e,z),c(e,H),c(e,at),c(e,v),e.removeAttribute("aria-hidden"),e.initialOffsetWidth=e.offsetWidth,c(e,D)):(c(e,H),e.removeAttribute("aria-hidden")),this.tooltipping=setTimeout(()=>{t.transition?(d(e,z,()=>{g(e,H),g(e,at),g(e,v),e.setAttribute("aria-hidden",!0),this.fading=!1},{once:!0}),g(e,D),this.fading=!0):(g(e,H),e.setAttribute("aria-hidden",!0)),this.tooltipping=!1},1e3),this)},toggle(t=null){return this.imageData.ratio===1?this.zoomTo(this.imageData.oldRatio,!0,null,t):this.zoomTo(1,!0,null,t),this},reset(){return this.viewed&&!this.played&&(this.imageData=C({},this.initialImageData),this.renderImage()),this},update(){const{element:t,options:e,isImg:i}=this;if(i&&!t.parentNode)return this.destroy();const s=[];if(w(i?[t]:t.querySelectorAll("img"),n=>{p(e.filter)?e.filter.call(this,n)&&s.push(n):this.getImageURL(n)&&s.push(n)}),!s.length)return this;if(this.images=s,this.length=s.length,this.ready){const n=[];if(w(this.items,(o,a)=>{const r=o.querySelector("img"),l=s[a];l&&r?(l.src!==r.src||l.alt!==r.alt)&&n.push(a):n.push(a)}),O(this.list,{width:"auto"}),this.initList(),this.isShown)if(this.length){if(this.viewed){const o=n.indexOf(this.index);if(o>=0)this.viewed=!1,this.view(Math.max(Math.min(this.index-o,this.length-1),0));else{const a=this.items[this.index];c(a,et),a.setAttribute("aria-selected",!0)}}}else this.image=null,this.viewed=!1,this.index=0,this.imageData={},this.canvas.innerHTML="",this.title.innerHTML=""}else this.build();return this},destroy(){const{element:t,options:e}=this;return t[f]?(this.destroyed=!0,this.ready?(this.played&&this.stop(),e.inline?(this.fulled&&this.exit(),this.unbind()):this.isShown?(this.viewing&&(this.imageRendering?this.imageRendering.abort():this.imageInitializing&&this.imageInitializing.abort()),this.hiding&&this.transitioning.abort(),this.hidden()):this.showing&&(this.transitioning.abort(),this.hidden()),this.ready=!1,this.viewer.parentNode.removeChild(this.viewer)):e.inline&&(this.delaying?this.delaying.abort():this.initializing&&this.initializing.abort()),e.inline||b(t,Z,this.onStart),t[f]=void 0,this):this}},Se={getImageURL(t){let{url:e}=this.options;return Q(e)?e=t.getAttribute(e):p(e)?e=e.call(this,t):e="",e},enforceFocus(){this.clearEnforceFocus(),d(document,Ct,this.onFocusin=t=>{const{viewer:e}=this;let{target:i}=t;if(!(i===document||i===e||e.contains(i))){for(;i;){if(i.getAttribute("tabindex")!==null||i.getAttribute("aria-modal")==="true")return;i=i.parentElement}e.focus()}})},clearEnforceFocus(){this.onFocusin&&(b(document,Ct,this.onFocusin),this.onFocusin=null)},open(){const{body:t}=this;c(t,Nt),this.scrollbarWidth>0&&(t.style.paddingRight=`${this.scrollbarWidth+(parseFloat(this.initialBodyComputedPaddingRight)||0)}px`)},close(){const{body:t}=this;g(t,Nt),this.scrollbarWidth>0&&(t.style.paddingRight=this.initialBodyPaddingRight)},shown(){const{element:t,options:e,viewer:i}=this;this.fulled=!0,this.isShown=!0,this.render(),this.bind(),this.showing=!1,e.focus&&(i.focus(),this.enforceFocus()),p(e.shown)&&d(t,Mt,e.shown,{once:!0}),T(t,Mt)!==!1&&this.ready&&this.isShown&&!this.hiding&&this.view(this.index)},hidden(){const{element:t,options:e,viewer:i}=this;e.fucus&&this.clearEnforceFocus(),this.fulled=!1,this.viewed=!1,this.isShown=!1,this.close(),this.unbind(),c(i,F),i.removeAttribute("role"),i.removeAttribute("aria-labelledby"),i.removeAttribute("aria-modal"),i.setAttribute("aria-hidden",!0),this.resetList(),this.resetImage(),this.hiding=!1,this.destroyed||(p(e.hidden)&&d(t,$t,e.hidden,{once:!0}),T(t,$t,null,{cancelable:!1}))},requestFullscreen(t){const e=this.element.ownerDocument;if(this.fulled&&!(e.fullscreenElement||e.webkitFullscreenElement||e.mozFullScreenElement||e.msFullscreenElement)){const{documentElement:i}=e;i.requestFullscreen?X(t)?i.requestFullscreen(t):i.requestFullscreen():i.webkitRequestFullscreen?i.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT):i.mozRequestFullScreen?i.mozRequestFullScreen():i.msRequestFullscreen&&i.msRequestFullscreen()}},exitFullscreen(){const t=this.element.ownerDocument;this.fulled&&(t.fullscreenElement||t.webkitFullscreenElement||t.mozFullScreenElement||t.msFullscreenElement)&&(t.exitFullscreen?t.exitFullscreen():t.webkitExitFullscreen?t.webkitExitFullscreen():t.mozCancelFullScreen?t.mozCancelFullScreen():t.msExitFullscreen&&t.msExitFullscreen())},change(t){const{options:e,pointers:i}=this,s=i[Object.keys(i)[0]];if(!s)return;const n=s.endX-s.startX,o=s.endY-s.startY;switch(this.action){case ot:(n!==0||o!==0)&&(this.pointerMoved=!0,this.move(n,o,t));break;case K:this.zoom(be(i),!1,null,t);break;case Kt:{this.action="switched";const a=Math.abs(n);a>1&&a>Math.abs(o)&&(this.pointers={},n>1?this.prev(e.loop):n<-1&&this.next(e.loop));break}}w(i,a=>{a.startX=a.endX,a.startY=a.endY})},isSwitchable(){const{imageData:t,viewerData:e}=this;return this.length>1&&t.x>=0&&t.y>=0&&t.width<=e.width&&t.height<=e.height}},Ie=M.Viewer,Ne=(t=>()=>(t+=1,t))(-1);class ct{constructor(e,i={}){if(!e||e.nodeType!==1)throw new Error("The first argument is required and must be an element.");this.element=e,this.options=C({},St,X(i)&&i),this.action=!1,this.fading=!1,this.fulled=!1,this.hiding=!1,this.imageClicked=!1,this.imageData={},this.index=this.options.initialViewIndex,this.isImg=!1,this.isShown=!1,this.length=0,this.moving=!1,this.played=!1,this.playing=!1,this.pointers={},this.ready=!1,this.rotating=!1,this.scaling=!1,this.showing=!1,this.timeout=!1,this.tooltipping=!1,this.viewed=!1,this.viewing=!1,this.wheeling=!1,this.zooming=!1,this.pointerMoved=!1,this.id=Ne(),this.init()}init(){const{element:e,options:i}=this;if(e[f])return;e[f]=this,i.focus&&!i.keyboard&&(i.focus=!1);const s=e.localName==="img",n=[];if(w(s?[e]:e.querySelectorAll("img"),o=>{p(i.filter)?i.filter.call(this,o)&&n.push(o):this.getImageURL(o)&&n.push(o)}),this.isImg=s,this.length=n.length,this.images=n,this.initBody(),q(document.createElement(f).style.transition)&&(i.transition=!1),i.inline){let o=0;const a=()=>{if(o+=1,o===this.length){let r;this.initializing=!1,this.delaying={abort:()=>{clearTimeout(r)}},r=setTimeout(()=>{this.delaying=!1,this.build()},0)}};this.initializing={abort:()=>{w(n,r=>{r.complete||(b(r,k,a),b(r,W,a))})}},w(n,r=>{if(r.complete)a();else{let l,h;d(r,k,l=()=>{b(r,W,h),a()},{once:!0}),d(r,W,h=()=>{b(r,k,l),a()},{once:!0})}})}else d(e,Z,this.onStart=({target:o})=>{o.localName==="img"&&(!p(i.filter)||i.filter.call(this,o))&&this.view(this.images.indexOf(o))})}build(){if(this.ready)return;const{element:e,options:i}=this,s=e.parentNode,n=document.createElement("div");n.innerHTML=te;const o=n.querySelector(`.${f}-container`),a=o.querySelector(`.${f}-title`),r=o.querySelector(`.${f}-toolbar`),l=o.querySelector(`.${f}-navbar`),h=o.querySelector(`.${f}-button`),m=o.querySelector(`.${f}-canvas`);if(this.parent=s,this.viewer=o,this.title=a,this.toolbar=r,this.navbar=l,this.button=h,this.canvas=m,this.footer=o.querySelector(`.${f}-footer`),this.tooltipBox=o.querySelector(`.${f}-tooltip`),this.player=o.querySelector(`.${f}-player`),this.list=o.querySelector(`.${f}-list`),o.id=`${f}${this.id}`,a.id=`${f}Title${this.id}`,c(a,i.title?st(Array.isArray(i.title)?i.title[0]:i.title):F),c(l,i.navbar?st(i.navbar):F),tt(h,F,!i.button),i.keyboard&&h.setAttribute("tabindex",0),i.backdrop&&(c(o,`${f}-backdrop`),!i.inline&&i.backdrop!=="static"&&pt(m,lt,"hide")),Q(i.className)&&i.className&&i.className.split(wt).forEach(u=>{c(o,u)}),i.toolbar){const u=document.createElement("ul"),E=X(i.toolbar),N=it.slice(0,3),I=it.slice(7,9),A=it.slice(9);E||c(r,st(i.toolbar)),w(E?i.toolbar:it,(S,L)=>{const x=E&&X(S),R=E?Et(L):S,V=x&&!q(S.show)?S.show:S;if(!V||!i.zoomable&&N.indexOf(R)!==-1||!i.rotatable&&I.indexOf(R)!==-1||!i.scalable&&A.indexOf(R)!==-1)return;const $=x&&!q(S.size)?S.size:S,ut=x&&!q(S.click)?S.click:S,_=document.createElement("li");i.keyboard&&_.setAttribute("tabindex",0),_.setAttribute("role","button"),c(_,`${f}-${R}`),p(ut)||pt(_,lt,R),y(V)&&c(_,st(V)),["small","large"].indexOf($)!==-1?c(_,`${f}-${$}`):R==="play"&&c(_,`${f}-large`),p(ut)&&d(_,Z,ut),u.appendChild(_)}),r.appendChild(u)}else c(r,F);if(!i.rotatable){const u=r.querySelectorAll('li[class*="rotate"]');c(u,G),w(u,E=>{r.appendChild(E)})}if(i.inline)c(h,ie),O(o,{zIndex:i.zIndexInline}),window.getComputedStyle(s).position==="static"&&O(s,{position:"relative"}),s.insertBefore(o,e.nextSibling);else{c(h,ee),c(o,mt),c(o,at),c(o,F),O(o,{zIndex:i.zIndex});let{container:u}=i;Q(u)&&(u=e.ownerDocument.querySelector(u)),u||(u=this.body),u.appendChild(o)}if(i.inline&&(this.render(),this.bind(),this.isShown=!0),this.ready=!0,p(i.ready)&&d(e,Rt,i.ready,{once:!0}),T(e,Rt)===!1){this.ready=!1;return}this.ready&&i.inline&&this.view(this.index)}static noConflict(){return window.Viewer=Ie,ct}static setDefaults(e){C(St,X(e)&&e)}}C(ct.prototype,Ee,ye,ve,Te,Se);function Ae(){document.querySelectorAll(".screen").forEach(e=>{const i=document.createElement("code");for(i.className="language-typescript";e.firstChild;)i.appendChild(e.firstChild);if(e.appendChild(i),i.offsetHeight>window.innerHeight*32/100){e.classList.add("fold");const s=document.createElement("div"),n=document.createElement("div");s.className="foldButton",n.className="divider",s.innerText="查看更多",e.appendChild(s),e.appendChild(n),s.addEventListener("click",()=>{e.classList.replace("fold","expand"),e.removeChild(s),e.removeChild(n)})}})}Ae();Qt.highlightAll();window.addEventListener("DOMContentLoaded",function(){Array.from(document.getElementsByTagName("img")).forEach((s,n)=>{s.dataset.original=s.src});const e=document.getElementsByClassName("nested0")[0],i=new ct(e,{filter(s){return s.id.length>0},fullscreen:!1,movable:!1,zoomable:!0,toggleOnDblclick:!0,navbar:!1,toolbar:!1,button:!1,title:function(s){return`${this.index+1}/${this.length}`},keyboard:!1,rotatable:!1,scalable:!1,initialCoverage:.75,minZoomRatio:.15,maxZoomRatio:1.2});window.checkPreview=()=>document.body.className==="viewer-open",window.closePreview=()=>{i.hide()}});function Ce(){const t=document.querySelectorAll(".figcap");t.forEach((e,i)=>{var n;const s=(n=e.nextElementSibling)==null?void 0:n.nextElementSibling;s&&s.insertAdjacentElement("afterend",e)}),t.forEach(e=>{e.classList.add("figcap-show")}),t.forEach(e=>{e.style.display="block"})}Ce();const xe=document.getElementsByTagName("object"),De=Array.from(xe);De.forEach(t=>{const e=document.createElement("video");e.controls=!0;const i=t.getAttribute("data");i&&(e.src=i);const s=t.getElementsByTagName("param");for(let o=0;o{dt.src=t.matches?"../../common/image/f_icon_dark.png":"../../common/image/f_icon.png"};Tt(vt);vt.addEventListener("change",Tt);window.addEventListener("beforeunload",()=>{vt.removeEventListener("change",Tt)}); diff --git a/products/phone/src/main/resources/resfile/articlecols/common/fonts/JetBrainsMonoNL-Regular.ttf b/products/phone/src/main/resources/resfile/articlecols/common/fonts/JetBrainsMonoNL-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..70d2ec9e28d6e06e923629b78b8a12c12e841589 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/fonts/JetBrainsMonoNL-Regular.ttf differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/book_pages.svg b/products/phone/src/main/resources/resfile/articlecols/common/image/book_pages.svg new file mode 100644 index 0000000000000000000000000000000000000000..080232808468174dff622a41e2c76b9b66243563 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/image/book_pages.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/chevron_down_circle.svg b/products/phone/src/main/resources/resfile/articlecols/common/image/chevron_down_circle.svg new file mode 100644 index 0000000000000000000000000000000000000000..9bd3bdd15c272408a9adf148a7dfd964390796da --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/image/chevron_down_circle.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/exclamationmark_triangle.svg b/products/phone/src/main/resources/resfile/articlecols/common/image/exclamationmark_triangle.svg new file mode 100644 index 0000000000000000000000000000000000000000..aff73928cfe7b28436c47047fe35ada3b1ce73a6 --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/image/exclamationmark_triangle.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/f_dwen.png b/products/phone/src/main/resources/resfile/articlecols/common/image/f_dwen.png new file mode 100644 index 0000000000000000000000000000000000000000..30adcab7d2b282eb0273599a0f71b1fffd18c0b6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/image/f_dwen.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/f_dwen_dark.png b/products/phone/src/main/resources/resfile/articlecols/common/image/f_dwen_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8040012982fff8e0c0e4818c97deac58680d72ba Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/image/f_dwen_dark.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/f_icon.png b/products/phone/src/main/resources/resfile/articlecols/common/image/f_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..74ef5eb4362afb089e0d3b042a74985e88ccaa57 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/image/f_icon.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/f_icon_dark.png b/products/phone/src/main/resources/resfile/articlecols/common/image/f_icon_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0476236584f3507b45313166b9768742590fce Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/image/f_icon_dark.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/title_2.png b/products/phone/src/main/resources/resfile/articlecols/common/image/title_2.png new file mode 100644 index 0000000000000000000000000000000000000000..6767b73f671f2a7e6836c118be809ad4b9c8cc6e Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/image/title_2.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/image/title_3.png b/products/phone/src/main/resources/resfile/articlecols/common/image/title_3.png new file mode 100644 index 0000000000000000000000000000000000000000..278b5d10fe378b9ba25dda62eb0fafb36427c2a6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/articlecols/common/image/title_3.png differ diff --git a/products/phone/src/main/resources/resfile/articlecols/common/js/changehref.js b/products/phone/src/main/resources/resfile/articlecols/common/js/changehref.js new file mode 100644 index 0000000000000000000000000000000000000000..5e35e3ec2d0c54f79896fa50002ebc9d3154258f --- /dev/null +++ b/products/phone/src/main/resources/resfile/articlecols/common/js/changehref.js @@ -0,0 +1,49 @@ +import '../../common/dist/jquery.js'; +function getArticleJson() { + return $.getJSON('../../common/config/articleUrlConfig.json') + .done(function (data) { + return data; + }); +} + +function getGiteeJson() { + return $.getJSON('../../common/config/giteeUrlConfig.json') + .done(function (data) { + return data; + }); +} + +(function () { + const elements = document.querySelectorAll('a[rel="noopener noreferrer"]'); + Promise.all([getArticleJson(), getGiteeJson()]) + .then((res) => { + const articleUrlConfig = res[0]; + const giteeUrlConfig = res[1]; + elements.forEach((item) => { + let hrefValue = item.getAttribute('href'); + let type = 0; + if (!hrefValue) { + return; + } + if (hrefValue.includes('article')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (articleUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(articleUrlConfig[key], type); + }); + } else if (hrefValue.includes('gitee')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (giteeUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(giteeUrlConfig[key], type); + }); + } + }); + }); +})(); diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/001.mp4 b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/001.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..bbf406d1a417176837ea088974452b7f133adf99 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/001.mp4 differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/ai_speech.jpg b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/ai_speech.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc8f438780fc045ae484de3f8df19ad0f28d8078 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/ai_speech.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/pause.png b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..5d364df1ff40dc494484219ecdace961607bbc73 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/pause.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/phone.png b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/phone.png new file mode 100644 index 0000000000000000000000000000000000000000..43ba8bbdeac2fb8b92e9bfe9388db6b2d637247a Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/phone.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/play_circle_fill.png b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/play_circle_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..0e05824e1c210555744051eb79df27b1a041430e Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/play_circle_fill.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/stop_circle_fill.png b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/stop_circle_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..8a0f784ffa337a2c804854320644d15aea8e7487 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/image/stop_circle_fill.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.css b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.css new file mode 100644 index 0000000000000000000000000000000000000000..7d4cf38edc34cc95a98b43b7c762e80ec37ab257 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.css @@ -0,0 +1,144 @@ +/* body */ +.chapter { + background-color: rgb(241, 243, 245); +} + +.section { + padding: 0px 16px; +} + +.button { + margin-top: 24px; + display: flex; + justify-content: center; +} + +.section-button { + padding: 0 16px; + height: 40px; + border-radius: 26px; + background-color: #0a59f7; + display: inline-block; + color: #f1f3f5; + text-align: center; + line-height: 40px; + transition: background-color 0.2s ease, transform 0.2s ease; +} + +.section-button:active { + transform: scale(0.95); +} + +.bg-container { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + width: 100%; + height: 100%; + margin: 0 auto; + padding-top: 48px; + overflow: hidden; +} + +.title-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-weight: bold; +} + +.main-title { + font-size: 30px; + margin-bottom: 6px; + color: #303030; +} + +.sub-title { + font-size: 20px; + color: #5a5a5a; +} + +.wrap-phobe { + margin-top: 40px; + position: relative; +} + +.phone-border { + position: absolute; + top: -11px; + left: 50%; + transform: translateX(-50%); + width: 365px; + pointer-events: none; +} + +.phone-gif { + margin: auto; + width: 152px; + border-radius: 16px; +} + +.wrap-btn { + display: flex; + justify-content: space-between; + margin-top: 50px; + padding: 4px; + width: 108px; + height: 48px; + border-radius: 24px; + background-color: rgba(255, 255, 255, 0.8); + box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.1); +} + +.wrap-btn .video-btn { + width: 40px; + height: 40px; + border-radius: 50%; + background-size: auto 100%; + background-position: center; + background-repeat: no-repeat; + transition: all 0.2s ease; /* 平滑过渡 */ + cursor: pointer; +} + +/* 点击时触发 :active 伪类 */ +.video-btn:active { + transform: scale(0.9); /* 点击时缩小 */ +} + +#play-btn { + background-image: url(./image/play_circle_fill.png); +} + +#stop-btn { + background-image: url(./image/stop_circle_fill.png); +} + +.bottom-text { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 50px; + width: 100%; + color: #5a5a5a; + font-size: 14px; + line-height: 19px; + font-weight: 500; +} + +.bg-black .bottom-text { + color: rgba(255, 255, 255, 0.6); +} + +.bg-white .bottom-text { + color: rgba(0, 0, 0, 0.6); +} + +@media (prefers-color-scheme: dark) { + .wrap-btn { + background-color: rgba(255, 255, 255, 0.8); + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.html b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.html new file mode 100644 index 0000000000000000000000000000000000000000..ed750bb513a65411a42ca993fdf87fac32b9fabe --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.html @@ -0,0 +1,77 @@ + + + + + + + 开箱即用的AI语音播报能力 + + + + + + + +
+ +
+
开箱即用的AI语音播报能力
+
+ +
+
开箱即用的AI语音播报能力
+
承载最新亮点特性
+
+
+
+ + +
+
+

本期为大家介绍 TextToSpeech 组件,解锁自然流畅新体验。

+
+

播报体验

+

引擎集成“聆小珊”音色,完美模拟真人语速音调,针对大篇幅文本的转换也不会卡顿,而且在聆听过程中也不会有很重的“机械音”。

+

个性化定制,用户可以根据自身习惯自定义语速、音调等,并且在播放过程中支持暂停和续播,完美契合不同场景下的要求,而且结合 HarmonyOS 的语音识别能力,可实现文本与语音的双转换。

+

操作体验

+

操作简单方便,开箱即用。大家不需要很繁琐的步骤,提前准备好一段文字,点击播放按钮即可。现在让我们一起遨游在“听”的世界中吧。

+
+
+
+
AI语音播报能力
+
开箱即用
+
+
+ + +
+
+
+
+
+
+
通过点击立即体验按钮,直接在HMOS
+
世界中体验TextToSpeech案例
+
+
+
+
立即体验
+
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.js b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6af854e644a3a8dfde17825fce5938503aba2c54 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ai-voice-out-of-box/index.js @@ -0,0 +1,29 @@ +function jump() { + nativeActionData.jumpPage('component', 29, -1, 'TextToSpeech'); +} + +let video = document.getElementById('voice-video'); +let playBtn = document.getElementById('play-btn'); +let stopBtn = document.getElementById('stop-btn'); + +playBtn.addEventListener('click', () => { + if (video.paused) { + video.play(); + playBtn.style.backgroundImage = 'url(./image/pause.png)'; + } else { + video.pause(); + playBtn.style.backgroundImage = 'url(./image/play_circle_fill.png)'; + } +}); + +// 停止按钮点击事件 +stopBtn.addEventListener('click', () => { + video.pause(); + video.currentTime = 0; + playBtn.style.backgroundImage = 'url(./image/play_circle_fill.png)'; +}); + +// 视频自然结束时重置按钮 +video.addEventListener('ended', () => { + playBtn.style.backgroundImage = 'url(./image/play_circle_fill.png)'; +}); diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/banner.jpg b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d3e9b501b99554298257792b83ddaeb0be23404b Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/banner.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/framework.jpg b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/framework.jpg new file mode 100644 index 0000000000000000000000000000000000000000..42f9ae4ae6732dc6729440831b85afead0897c80 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/framework.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/framework.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/framework.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f7bb0b3fa0aae0d5aa6fae813f22aef29fec9 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/framework.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-2.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-2.png new file mode 100644 index 0000000000000000000000000000000000000000..daa8fb4d99f748795656cc9fdc14d0e01c05b89a Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-2.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-3.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-3.png new file mode 100644 index 0000000000000000000000000000000000000000..3efc9209a338da279641fed2c065f499aa137d6e Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-3.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-4.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-4.png new file mode 100644 index 0000000000000000000000000000000000000000..68c4d90d7efb1da672b85d114fe43dbec3118e25 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1-4.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1.png new file mode 100644 index 0000000000000000000000000000000000000000..1dab53f0904a51af80b32566678819b3f40e9c36 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/grid_C1.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-01.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-01.png new file mode 100644 index 0000000000000000000000000000000000000000..029f24728293bb177b2d7c877f04d58fbced72a4 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-01.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-03.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-03.png new file mode 100644 index 0000000000000000000000000000000000000000..622b90faac0b6b7fe46d82a3dd82a92828eadd90 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-03.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-04.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-04.png new file mode 100644 index 0000000000000000000000000000000000000000..34840bd5aa218d2229431cd4734f1abea0aa588e Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kaifa-04.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kf-testing.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kf-testing.png new file mode 100644 index 0000000000000000000000000000000000000000..f89dab92e0c7521fd3a3cb6257d3cc67db83f5c5 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/kf-testing.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/right-arrow.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/right-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..390d16b7903491ac6bb0a3f4c8ea39dfeb07a7e7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/right-arrow.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem1.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem1.png new file mode 100644 index 0000000000000000000000000000000000000000..ede991891f5b3b7f8d63f9d6aa82052294a766dc Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem1.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem2.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem2.png new file mode 100644 index 0000000000000000000000000000000000000000..f52ffec8d41342764e106906fc444283d7d6088b Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem2.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem4.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem4.png new file mode 100644 index 0000000000000000000000000000000000000000..cb0b3f36b299fdbacaf8546747b5e21dbca73e21 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem4.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem5.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem5.png new file mode 100644 index 0000000000000000000000000000000000000000..d893596b8941a8f7d65dfb7a132d92910a689116 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem5.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem6.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem6.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4383eaeb77e6b3928f350d7c7a41860e1f8640 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem6.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem8.png b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem8.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc6efdb31d3b88d6bd483fee2c3858b7d30c295 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/app-design-development/image/swiperItem8.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.css b/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.css new file mode 100644 index 0000000000000000000000000000000000000000..abb5dfed58db3131047648200c845de7340063a8 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.css @@ -0,0 +1,354 @@ +/* body */ +.chapter { + padding: 0 24px; + background-color: rgb(241, 243, 245); +} + +.section { + margin-top: 48px; +} + +.section-title { + font-size: 24px; +} + +.section-title .gradient { + background-clip: text; + -webkit-background-clip: text; + color: transparent; + font-size: 30px; + font-weight: bold; +} + +.gradient-blue { + background-image: linear-gradient( + to right, + rgb(61, 155, 254), + rgb(30, 101, 237) + ); +} + +.gradient-green { + background-image: linear-gradient( + to right, + rgb(76, 210, 253), + rgb(0, 196, 169) + ); +} + +.gradient-pink { + background-image: linear-gradient( + to right, + rgb(255, 125, 216), + rgb(214, 53, 117) + ); +} + +/* develop-card */ +.develop-container { + position: relative; + margin-top: 16px; + padding: 16px 24px; + width: 100%; + border-radius: 24px; + background-color: #fff; +} + +.develop-title { + font-size: 24px; + font-weight: 500; +} + +.develop-subtitle { + font-size: 16px; + font-weight: 500; +} + +.mg-t-16 { + margin-top: 16px; +} + +.develop-list { + margin: 8px 0; +} + +.develop-list-item { + height: 64px; + display: flex; + align-items: center; + justify-content: flex-start; +} + +.develop-icon { + width: 48px; + height: 48px; + margin-right: 8px; + border-radius: 12px; +} + +.list-title { + color: #000000e5; +} + +.list-body { + font-size: 14px; + color: #00000099; +} + +.develop-text, +.list-title, +.list-body { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.step-container { + margin: 16px 0; +} + +.step { + display: flex; + align-items: center; + margin-bottom: 32px; +} + +.step:last-child { + margin-bottom: 0; +} + +.step-title { + min-width: 30px; + font-size: 14px; + font-weight: 400; + color: #00000099; +} + +.dot { + margin: 0 8px; + position: relative; + width: 8px; + height: 8px; + background-color: #0a59f7ff; + border-radius: 4px; + flex-shrink: 0; +} + +.path { + position: absolute; + left: 3px; + top: 3px; + width: 2px; + height: 70px; + background-color: #0a59f7ff; +} + +.step-text { + font-size: 14px; + color: #00000099; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + text-overflow: ellipsis; +} + +.bg-framework { + background-color: rgb(226, 232, 251); +} + +/* swiper */ +.swiper { + width: 100%; + margin-top: 16px; + position: relative; + overflow: hidden; + text-align: center; + border-radius: 24px; + background: #fff; +} + +.swiper .card-wrap { + width: 100%; + height: 100%; + display: flex; +} + +.swiper .card-wrap .card-item { + color: #000; + padding: 16px; + width: 100%; + border-radius: 24px; + flex: 0 0 100%; +} + +.swiper .card-wrap .card-item .card-icon { + max-width: 100%; + margin-bottom: 16px; +} + +.swiper .card-wrap .card-item .card-title { + padding: 0 8px 8px 8px; + font-size: 26px; + line-height: 36px; + font-weight: bold; +} + +.swiper .card-wrap .card-item .card-desc { + padding: 0 8px; + font-size: 12px; + line-height: 16px; + opacity: 0.6; +} + +.swiper .swiper-pagination { + width: 100%; + height: 32px; + text-align: center; +} + +.swiper .swiper-pagination .swiper-point { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 4px; + margin: 0 3px; + background-color: rgba(0, 0, 0, 0.2); + transition: opacity 0.3s, background-color 0.3s, width 0.3s; +} + +.swiper-point-selected { + width: 12px !important; + background-color: rgb(10, 88, 246) !important; +} + +/* framework */ +.bg-framework { + margin-top: 16px; + border-radius: 16px; + height: 200px; /* 固定容器的高度 */ + overflow: hidden; /* 隐藏超出容器的部分 */ + position: relative; /* 相对定位,为子元素的绝对定位提供参考 */ +} +.container-image { + height: 100%; + position: absolute; /* 绝对定位 */ + top: 50%; /* 将图片的顶部定位到容器垂直中心 */ + left: 50%; /* 将图片的左侧定位到容器水平中心 */ + transform: translate( + -50%, + -50% + ); /* 将图片向左和向上移动自身宽度和高度的 50%,实现居中显示 */ +} +/* list-card */ +.list-card { + margin-top: 16px; + border-radius: 16px; + background-color: #fff; + overflow: hidden; +} + +.list-card:last-child { + margin-bottom: 16px; +} + +.list-card-item { + display: flex; + padding: 0 16px; + font-size: 14px; + height: 67px; + line-height: 67px; + border-bottom: 1px solid rgba(210, 210, 210, 0.5); +} + +.list-card-item:active { + background-color: #f8f8f8; +} + +.list-card-item .item-word { + display: flex; + flex-direction: column; + justify-content: center; + flex: 1; + height: 100%; +} + +.list-card-item .item-title { + font-size: 16px; + line-height: 19px; + font-weight: bold; +} + +.list-card-item .item-desc { + margin-top: 2px; + font-size: 14px; + line-height: 16px; + color: rgba(0, 0, 0, 0.6); + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; +} + +.list-card-item span { + vertical-align: middle; +} + +.list-card-item:last-child { + border-bottom: none; +} + +.detail-box { + color: #0009; + display: flex; + align-items: center; + flex: none; + margin-left: 8px; +} + +.detail-box::after { + display: inline-block; + content: ''; + /* background-color: #000000e6; */ + background: url('./image/right-arrow.png') + no-repeat center center; + background-size: cover; + width: 14px; + height: 14px; + vertical-align: middle; +} + +/* testing-card */ +.testing-card { + margin-top: 16px; + padding: 24px; + border-radius: 24px; + background: #fff; + color: #000; + text-align: center; +} + +.testing-card .card-icon { + max-width: 100%; + margin-bottom: 20px; +} + +.testing-card .card-title { + margin-bottom: 12px; + font-size: 26px; + line-height: 36px; + font-weight: bold; +} + +.testing-card .card-desc { + font-size: 14px; + line-height: 20px; + opacity: 0.6; +} + +@media (prefers-color-scheme: dark) { + .swiper .swiper-pagination .swiper-point { + background-color: rgba(255, 255, 255, 0.1); + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.html b/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.html new file mode 100644 index 0000000000000000000000000000000000000000..90e810fb8a29829f8fcbb895291fb1c31a421842 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.html @@ -0,0 +1,286 @@ + + + + + + + 设计与开发应用介绍 + + + + + + +
+ +
+
设计与开发应用介绍
+
+ +
+
帮助开发者打造高端精致、简单易用、
+
极致流畅、纯净安全的应用
+
+
+
+ + +
+
+
+
+ 开发准备 +
+
+
+
准备与学习
+
了解ArkTS和ArkUI
+
+
+ +
+
ArkTS
+
鸿蒙生态的应用开发语言。
+
+
+
+ +
+
ArkUI
+
+ 构建分布式应用界面的声明式UI开发框架。 +
+
+
+
+
开发套件-DevEco Studio
+
+
+ +
+
DevEco Studio
+
+ 一站式的HarmonyOS应用及元服务开发平台。 +
+
+
+
+
+
+
+
+
+ 赋能套件 +
+
+
+
赋能套件
+
+
+
+
+
感知
+
阶段
+
+
+
+
+
+ HarmonyOS白皮书、HarmonyOS应用开发知识地图 +
+
+
+
+
学习
+
阶段
+
+
+
+
+
+ HarmonyOS第一课、HarmonyOS应用开发快速入门、Codelabs +
+
+
+
+
开发
+
阶段
+
+
+
+
+
+ 开发指南、API参考、示例代码、最佳实践 +
+
+
+
+
学习
+
阶段
+
+
+
开发者社区&FAQ
+
+
+
+
+
+
+
+
+ 应用体验设计 +
+
+
+
+
+ +
新控件
+
+ 提供基于 HarmonyOS 版本风格的控件样式及设计规范,帮助你快速了解控件设计和基础能力。 +
+
+
+ +
实况窗设计
+
+ 实况窗设计包含了卡片态、胶囊态、沉浸态,帮助用户聚焦进行中任务、方便快速查看和即时处理。 +
+
+
+ +
一键登录
+
+ 华为账号提供登录设计规范,保障 HarmonyOS + 应用拥有简单易用、高效一致、快速安全的登录体验。 +
+
+
+ +
元服务设计
+
+ 轻量高效、即点即用,多形态的组件构成样式为用户提供丰富便捷的应用服务。 +
+
+
+ +
多窗
+
+ 包含悬浮窗、分屏不同的窗口形态,为你提供灵活高效的多任务并行体验。 +
+
+
+ +
分享
+
+ 为各场景的内容分享体验提供设计规范,帮助你了解系统分享能力。 +
+
+
+
+ + + + + + +
+
+
+
+
+
+ 应用架构 +
+
+
+
+ +
+
+
+
+
+
+ 功能开发 +
+
+
+
+
+ 文档 + 探索最新操作文档,掌握开发、上架、审核全流程。 +
+ 详情 +
+
+
+ 学堂 + 学、练、考、证全流程服务,让你快速成为 HarmonyOS 人才。 +
+ 详情 +
+
+
+ 社区 + 提出你的问题,与开发者深入交流,同步探索热门话题。 +
+ 详情 +
+
+
+ 活动 + 与专家深度交流,结识行业大咖,了解一手资讯。 +
+ 详情 +
+
+
+
+
+
+ 应用测试 +
+
+
+ +
DevEco Testing
+
+ DevEco Testing + 是一站式的应用测试服务平台。为你提供自动化测试框架,及稳定性、性能等专项测试服务,覆盖应用测试全周期,助力打造高品质应用。 +
+
+
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.js b/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.js new file mode 100644 index 0000000000000000000000000000000000000000..df0ece44f03cc2f567e5eb198a9c5e1f9f6627d7 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/app-design-development/index.js @@ -0,0 +1,175 @@ +import '../../articlecols/common/dist/jquery.js'; +const swiper = document.getElementsByClassName('card-wrap')[0]; +const pointElements = document.getElementsByClassName('swiper-point'); +const points = Array.from(pointElements); + +let startX; +let startY; +let scrollX; +let current = 0; +let scrollDir; +let startTime; +let maxDeltaX = 0; +let isDragging = false; + +function getArticleJson() { + return $.getJSON('../../articlecols/common/config/articleUrlConfig.json') + .done(function (data) { + return data; + }); +} + +function getGiteeJson() { + return $.getJSON('../../articlecols/common/config/giteeUrlConfig.json') + .done(function (data) { + return data; + }); +} + +(function () { + const elements = Array.prototype.slice.call(document.getElementsByClassName('detail-box')); + Promise.all([getArticleJson(), getGiteeJson()]) + .then((res) => { + const articleUrlConfig = res[0]; + const giteeUrlConfig = res[1]; + elements.forEach((item) => { + let hrefValue = item.getAttribute('href'); + let type = 0; + if (!hrefValue) { + return; + } + if (hrefValue.includes('article')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (articleUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(articleUrlConfig[key], type); + }); + } else if (hrefValue.includes('gitee')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (giteeUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(giteeUrlConfig[key], type); + }); + } + }); + }); +})(); + +function handleStart(e) { + const client = e.touches ? e.touches[0] : e; + startTime = Date.now(); + startX = client.clientX; + startY = client.clientY; + swiper.style.transition = 'none'; + + isDragging = true; + + // 鼠标事件动态绑定移动和结束监听 + if (!e.touches) { + document.addEventListener('mousemove', handleMove); + document.addEventListener('mouseup', handleEnd); + } +} + +function handleMove(e) { + // 只在拖动时处理 + if (!isDragging) { + return; + } + + const client = e.touches ? e.touches[0] : e; + const deltaX = client.clientX - startX; + const deltaY = client.clientY - startY; + + if (!scrollDir) { + scrollDir = Math.abs(deltaX) > Math.abs(deltaY) ? 'x' : 'y'; + } + + if (scrollDir === 'x') { + // 横向滚动阻止默认 + e.preventDefault(); + maxDeltaX = Math.abs(deltaX) > Math.abs(maxDeltaX) ? deltaX : maxDeltaX; + scrollX = client.clientX; + swiper.style.transform = `translateX(${current * -100 + }%) translateX(${deltaX}px)`; + } +} + +function handleEnd(e) { + if (!isDragging) { + return; + } + e.preventDefault(); + const endTime = Date.now(); + swiper.style.transition = 'transform 0.4s ease'; + + if (scrollDir === 'x') { + const delta = scrollX - startX; + const isFlick = endTime - startTime < 200; + const isReverseSwipe = Math.abs(maxDeltaX) - Math.abs(delta) > 20; + + if (isFlick) { + if (Math.abs(delta) > 10) { + updateCurrent(delta > 0 ? -1 : 1); + } + } else if (isReverseSwipe) { + // 取消滑动 + } else { + if (Math.abs(delta) > 50) { + updateCurrent(delta > 0 ? -1 : 1); + } + } + + swiper.style.transform = `translateX(${current * -100}%)`; + setPointSelect(current); + } + + // 重置状态 + scrollDir = undefined; + maxDeltaX = 0; + isDragging = false; + + // 移除鼠标监听 + if (!e.touches) { + document.removeEventListener('mousemove', handleMove); + document.removeEventListener('mouseup', handleEnd); + } +} + +function updateCurrent(step) { + const newIndex = current + step; + current = Math.max(0, Math.min(newIndex, swiper.children.length - 1)); +} + +swiper.addEventListener('touchstart', handleStart); +swiper.addEventListener('touchmove', handleMove); +swiper.addEventListener('touchend', handleEnd); + +swiper.addEventListener('mousedown', handleStart); + +points.forEach((item, index) => { + item.addEventListener('click', () => { + current = index; + // 重置swiper的位置以匹配动画开始前的位置(尽管这里可能看起来多余,但保持一致性是个好习惯) + swiper.style.transform = `translateX(${current * -100}%) translateX(0px)`; + // 设置过渡效果并触发动画 + swiper.style.transition = 'transform 0.4s ease'; + requestAnimationFrame(() => { + swiper.style.transform = `translateX(${current * -100}%)`; + }); + + setPointSelect(index); + }); +}); + +function setPointSelect(currentIndex) { + points.forEach((item, index) => { + item.classList.toggle('swiper-point-selected', index === currentIndex); + }); +} diff --git a/products/phone/src/main/resources/resfile/bannercols/common/css/banner.css b/products/phone/src/main/resources/resfile/bannercols/common/css/banner.css new file mode 100644 index 0000000000000000000000000000000000000000..abd4cad49f1f501fc0c0fc98b42710b2a857ea8f --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/common/css/banner.css @@ -0,0 +1,140 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + max-width: 730px; + margin: auto; + background-color: #f1f3f5; +} + +a { + text-decoration: none; +} + +.main-content { + padding-top: 108px; + font-family: sans-serif; +} + +/* header */ +.title { + word-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; +} + +.image-cover { + background-size: cover; + background-position: center; +} + +.header .title { + margin: 0 16px; + font-size: 24px; + line-height: 28px; + font-weight: bold; +} + +.header .content { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + position: relative; + margin-top: 16px; + width: 100%; + height: 462px; + overflow: hidden; +} + +.banner-img { + height: 100%; +} + +.header .content .image-words { + position: absolute; + bottom: 40px; + text-align: center; + color: #ffffffe6; + font-size: 16px; + line-height: 19px; +} + +/* text */ +.divider { + height: 1px; + background-color: rgba(0, 0, 0, 0.1); + margin: 24px 0px; +} + +/* h3 */ +h3 { + display: flex; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + margin-top: 1.5rem; + font-size: 1.125rem; + color: #000000e6; + line-height: 1; +} + +h3::before { + display: inline-block; + content: ''; + margin-right: 8px; + width: 1.625rem; + height: 1.625rem; + background-image: url(../image/title_3.png); + background-size: cover; +} + +.text { + margin-top: 1.5rem; + line-height: 1.875rem; + color: #00000099; + overflow-wrap: break-word; +} + +/* footer */ +.footer { + display: flex; + flex-direction: column; + align-items: center; + margin: 48px 24px 76px; + border-radius: 16px; + overflow: hidden; + background-color: #e5e5ea; + background-image: url(../image/f_dwen.png); + background-repeat: repeat; +} + +.footerImg { + max-width: 328px; +} + +@media (prefers-color-scheme: dark) { + .footer { + background-image: url(../image/f_dwen_dark.png); + } + .divider { + background-color: rgba(255, 255, 255, 0.2); + } +} + +@media screen and (min-width: 600px) { + .footerImg { + max-width: calc(1.2 * 328px); + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/common/image/f_dwen.png b/products/phone/src/main/resources/resfile/bannercols/common/image/f_dwen.png new file mode 100644 index 0000000000000000000000000000000000000000..30adcab7d2b282eb0273599a0f71b1fffd18c0b6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/common/image/f_dwen.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/common/image/f_dwen_dark.png b/products/phone/src/main/resources/resfile/bannercols/common/image/f_dwen_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8040012982fff8e0c0e4818c97deac58680d72ba Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/common/image/f_dwen_dark.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/common/image/f_icon.png b/products/phone/src/main/resources/resfile/bannercols/common/image/f_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..74ef5eb4362afb089e0d3b042a74985e88ccaa57 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/common/image/f_icon.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/common/image/f_icon_dark.png b/products/phone/src/main/resources/resfile/bannercols/common/image/f_icon_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0476236584f3507b45313166b9768742590fce Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/common/image/f_icon_dark.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/common/image/title_3.png b/products/phone/src/main/resources/resfile/bannercols/common/image/title_3.png new file mode 100644 index 0000000000000000000000000000000000000000..278b5d10fe378b9ba25dda62eb0fafb36427c2a6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/common/image/title_3.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/common/js/banner.js b/products/phone/src/main/resources/resfile/bannercols/common/js/banner.js new file mode 100644 index 0000000000000000000000000000000000000000..f45b8f30ab91794d9347567da03e0e65f1738d04 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/common/js/banner.js @@ -0,0 +1,10 @@ +const footerImg = document.getElementsByClassName('footerImg')[0]; +const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)'); +const handleFooterImgChange = (e) => { + footerImg.src = e.matches ? '../common/image/f_icon_dark.png' : '../common/image/f_icon.png'; +}; +handleFooterImgChange(darkModeQuery); +darkModeQuery.addEventListener('change', handleFooterImgChange); +window.addEventListener('beforeunload', () => { + darkModeQuery.removeEventListener('change', handleFooterImgChange); +}); \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/header.png b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/header.png new file mode 100644 index 0000000000000000000000000000000000000000..3079d92dd935014b55b5f74d67ff3830be27e72d Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/header.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/path.jpg b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/path.jpg new file mode 100644 index 0000000000000000000000000000000000000000..04c0ce053053ff8a7e6c0157a90ea0eff15c6e62 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/path.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/position.png b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/position.png new file mode 100644 index 0000000000000000000000000000000000000000..2a252a441e3b6e3b5aca71510fbe92d9d09918ff Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/image/position.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.css b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.css new file mode 100644 index 0000000000000000000000000000000000000000..03a61e99176b483854fb5e6a11ed6bd1873a532f --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.css @@ -0,0 +1,442 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + /* 字体 */ + font-family: sans-serif; +} + +html { + margin: 108px auto 0; + background-color: #f3f4f6; + min-width: 302px; + max-width: 730px; + overflow: scroll; + scrollbar-width: none; +} + +.header { + position: relative; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin: auto; + width: 100%; + height: 152px; + overflow: hidden; +} + +.header-img { + height: 100%; +} + +.title-container { + position: absolute; + top: 48px; + width: 100%; +} + +.main-title { + background: linear-gradient(180deg, rgb(27, 70, 235), rgb(51, 169, 254)); + -webkit-background-clip: text; + color: transparent; + font-weight: bold; + font-size: 30px; + text-align: center; +} + +.sub-title { + margin-top: 2px; + font-size: 10px; + color: rgba(0, 0, 0, 0.6); + text-align: center; +} + +.chapter { + position: relative; + padding-bottom: 28px; + margin: auto; + overflow: hidden; +} + +.chapter-img { + position: absolute; + top: 0; + width: 740px; + height: 1206px; + left: 50%; + transform: translateX(-50%); /* 水平向左移动自身宽度的50% */ + z-index: -1; +} + +.section { + margin-top: 36px; + position: relative; +} + +.section:nth-child(1) { + margin-top: 22px; +} + +.catalog-title { + display: flex; + font-weight: bold; + justify-content: center; +} + +/* 标题位移 */ + +.title1 { + transform: translateX(-9px); +} + +.title2 { + transform: translateX(-31px); +} + +.title3 { + transform: translateX(61px); +} + +.title4 { + transform: translateX(4px); +} + +.title-text:first-child { + margin-right: 6px; +} + +.title-icon:first-child { + margin-right: 6px; +} + +.title-icon { + display: inline-block; + width: 26px; + height: 26px; +} + +.title-text { + display: inline-block; + height: 26px; + background: radial-gradient(rgba(24, 63, 234, 1), rgba(24, 63, 234, 0.6)); + box-shadow: inset -0.9px -0.9px 2.1px 0px rgba(175, 189, 255, 1); + padding: 4px 6px; + font-size: 16px; + font-weight: bold; + color: white; + border-radius: 6px; + line-height: 18px; +} + +.catalog-container { + max-width: 730px; + margin: 16px 24px 0 24px; + padding: 12px 12px 0 12px; + background-color: rgba(255, 255, 255, 0.4); + border: 1px solid #fff; + border-radius: 16px; + backdrop-filter: blur(5px); + /* 模糊半径为 5 像素 */ + box-shadow: 0 0 10px 0 rgba(10, 89, 247, 0.31); +} + +.catalog-item { + margin-bottom: 12px; + color: rgba(0, 0, 0, 0.9); +} + +.item-title { + font-weight: bold; + font-size: 14px; +} + +.item-title span { + color: rgb(10, 89, 247); +} + +.item-detail { + font-size: 12px; + line-height: 22px; +} + +/* footer */ +.footer { + display: flex; + flex-direction: column; + align-items: center; + margin: 48px 24px 76px; + border-radius: 16px; + overflow: hidden; + background-color: rgba(226, 226, 226, 0.7); + background-image: url(../common/image/f_dwen.png); + background-repeat: repeat; +} + +.footerImg { + max-width: 328px; +} + +@media screen and (min-width: 600px) { + .footerImg { + max-width: calc(1.2 * 328px); + } +} + +@media screen and (min-width: 350px) { + .title1 { + transform: translateX(-9px); + } + + .title2 { + transform: translateX(-31px); + } + + .title3 { + transform: translateX(61px); + } + + .title4 { + transform: translateX(-17px); + } +} + +@media screen and (min-width: 386px) { + .title1 { + transform: translateX(-9px); + } + + .title2 { + transform: translateX(-31px); + } + + .title3 { + transform: translateX(61px); + } + + .title4 { + transform: translateX(-43px); + } +} + +@media screen and (min-width: 398px) { + .title1 { + transform: translateX(-9px); + } + + .title2 { + transform: translateX(-31px); + } + + .title3 { + transform: translateX(61px); + } + + .title4 { + transform: translateX(-65px); + } +} + +@media screen and (min-width: 417px) { + .title1 { + transform: translateX(-33px); + } + + .title2 { + transform: translateX(-27px); + } + + .title3 { + transform: translateX(59px); + } + + .title4 { + transform: translateX(-65px); + } +} + +@media screen and (min-width: 418px) { + .title1 { + transform: translateX(-33px); + } + + .title2 { + transform: translateX(59px); + } + + .title3 { + transform: translateX(33px); + } + + .title4 { + transform: translateX(-71px); + } +} + +@media screen and (min-width: 433px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(50px); + } + + .title3 { + transform: translateX(30px); + } + + .title4 { + transform: translateX(-73px); + } +} + +@media screen and (min-width: 434px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(50px); + } + + .title3 { + transform: translateX(-10px); + } + + .title4 { + transform: translateX(-73px); + } +} +@media screen and (min-width: 449px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(50px); + } + + .title3 { + transform: translateX(-8px); + } + + .title4 { + transform: translateX(-73px); + } +} +@media screen and (min-width: 478px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(50px); + } + + .title3 { + transform: translateX(-11px); + } + + .title4 { + transform: translateX(-73px); + } +} +@media screen and (min-width: 494px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(50px); + } + + .title3 { + transform: translateX(-93px); + } + + .title4 { + transform: translateX(-64px); + } +} + +@media screen and (min-width: 503px) { + .title1 { + transform: translateX(-7px); + } + + .title2 { + transform: translateX(60px); + } + + .title3 { + transform: translateX(-93px); + } + + .title4 { + transform: translateX(-63px); + } +} + +@media screen and (min-width: 504px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(130px); + } + + .title3 { + transform: translateX(-143px); + } + + .title4 { + transform: translateX(-43px); + } +} + +@media screen and (min-width: 509px) { + .title1 { + transform: translateX(-8px); + } + + .title2 { + transform: translateX(130px); + } + + .title3 { + transform: translateX(-145px); + } + + .title4 { + transform: translateX(-44px); + } +} + +@media screen and (min-width: 517px) { + .title1 { + transform: translateX(-7px); + } + + .title2 { + transform: translateX(130px); + } + + .title3 { + transform: translateX(-143px); + } + + .title4 { + transform: translateX(-44px); + } +} + +@media (prefers-color-scheme: dark) { + .footer { + background-image: url(../common/image/f_dwen_dark.png); + } +} diff --git a/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.html b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.html new file mode 100644 index 0000000000000000000000000000000000000000..395aa9428498f5798da68e247d7115adbd7ef6f4 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.html @@ -0,0 +1,150 @@ + + + + + + + HarmonyOS赋能套件 + + + + + + + + +
+ +
+
HarmonyOS赋能套件
+
+ 打造业界一流的开发者赋能产品,助力开发者开发卓越应用 +
+
+
+ +
+ + +
+ +
+ +
感知阶段
+
+ +
+
+
+ 1. HarmonyOS 白皮书 +
+
+ 快速、准确、全面地掌握HarmonyOS开发的关键技术、核心理念以及解决方案。 +
+
+ +
+
+ 2. HarmonyOS应用开发知识地图 +
+
+ 构建HarmonyOS学习路径,导航HarmonyOS开发的星辰大海。 +
+
+
+
+ + +
+ +
+ +
学习阶段
+
+ +
+
+
+ 3. HarmonyOS 第一课 +
+ 采用多维融合的教学方式,提供所见即所得的代码讲解和即学即练的操作实践。 +
+ +
+
+ 4. HarmonyOS应用开发快速入门 +
+ 通过手把手的教学实践案例,从零开始,掌握应用开发的必备技能。 +
+ +
+
5. Codelabs
+ 动手学编程,每一行代码都是成长的印记。 +
+
+
+ + +
+ +
+
开发阶段
+ +
+ +
+
+
6. 开发指南
+ 提供丰富的场景化的开发指导,助力开发者高效开发应用。 +
+ +
+
7. API参考
+ 提供全量的组件、接口、错误码参考,方便开发者快速查找所需的API信息。 +
+ +
+
8. 示例代码
+ 提供可直接引用的示例代码,加速开发进程,简化编程工作。 +
+ +
+
9. 最佳实践
+ 汇集实战经验,覆盖高频开发场景,提升开发效率。 +
+
+
+ + +
+ +
+ +
支持阶段
+
+ +
+
+
10. 开发者社区
+ 提供丰富的场景化开发指导,助力开发者高效开发应用。 +
+ +
+
11. AI智能回答
+ 提供全量的组件、接口、错误码参考,方便开发者快速查找所需的API信息。 +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.js b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.js new file mode 100644 index 0000000000000000000000000000000000000000..9c4462679f96667254a728a4421cb37657740756 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/harmonyos-empowerment/index.js @@ -0,0 +1,45 @@ +import '../../articlecols/common/dist/jquery.js'; +function getArticleJson() { + return $.getJSON('../../articlecols/common/config/articleUrlConfig.json') + .done(function (data) { + return data; + }); +} +function getGiteeJson() { + return $.getJSON('../../articlecols/common/config/giteeUrlConfig.json') + .done(function (data) { + return data; + }); +} +const btnList = Array.from(document.getElementsByClassName('catalog-item')); +Promise.all([getArticleJson(), getGiteeJson()]) + .then((res) => { + const articleUrlConfig = res[0]; + const giteeUrlConfig = res[1]; + btnList.forEach((item) => { + let hrefValue = item.getAttribute('url'); + let type = 0; + if (!hrefValue) { + return; + } + if (hrefValue.includes('article')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (articleUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(articleUrlConfig[key], type); + }); + } else if (hrefValue.includes('gitee')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (giteeUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(giteeUrlConfig[key], type); + }); + } + }); + }); diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/article.jpg b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/article.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a1677f5b1e2fe77a7e76dc368c40cc71bff7ab20 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/article.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/banner.jpg b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e6388c798e603294ab63777f77cbe75ec9343b1e Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/banner.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_1.png b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_1.png new file mode 100644 index 0000000000000000000000000000000000000000..464f7f804e0c16b960236400bbf7b9a9efa1f95e Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_1.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_2.png b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_2.png new file mode 100644 index 0000000000000000000000000000000000000000..90427c434e45c36f26a62bccc0734c6022f15544 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_2.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_3.png b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_3.png new file mode 100644 index 0000000000000000000000000000000000000000..922e01930f646eb13b65438e6caf8ed46e94760b Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/bg_3.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/phone.png b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/phone.png new file mode 100644 index 0000000000000000000000000000000000000000..43ba8bbdeac2fb8b92e9bfe9388db6b2d637247a Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/phone.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/video_comp.gif b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/video_comp.gif new file mode 100644 index 0000000000000000000000000000000000000000..576fdd749eaa6a14f678698251443a50d429d71c Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/video_comp.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/video_devp.gif b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/video_devp.gif new file mode 100644 index 0000000000000000000000000000000000000000..e7ed50eccbcc1c50d9ea5bf139485ccd5ba37c59 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/hmos-world/image/video_devp.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/index.css b/products/phone/src/main/resources/resfile/bannercols/hmos-world/index.css new file mode 100644 index 0000000000000000000000000000000000000000..5d0709f95703489c9302ec2cb98697ec496e5c41 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/hmos-world/index.css @@ -0,0 +1,102 @@ +/* body */ +.section { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + position: relative; + width: 100%; + height: 648px; + overflow: hidden; +} + +.bg-img { + position: absolute; + top: 0; + height: 100%; +} + +.bg-container { + position: relative; + width: 100%; + height: 100%; + margin: 0 auto; + padding-top: 48px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; + z-index: 99; +} + +.title-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-weight: bold; +} + +.main-title { + font-size: 30px; + margin-bottom: 12px; + color: #000000e6; +} + +.sub-title { + font-size: 20px; + color: #00000099; + max-width: 264px; + text-align: center; +} + +.phone-border { + position: absolute; + top: 171px; + left: 50%; + transform: translateX(-50%); + width: 360px; +} + +.phone-gif { + position: absolute; + top: 182px; + left: 50%; + transform: translateX(-50%); + width: 151px; + border-radius: 18px; +} + +.bottom-text { + position: absolute; + width: 100%; + bottom: 46px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: #00000099; + font-size: 14px; + line-height: 19px; +} + +.text-safety { + width: 295px; + text-align: center; +} + +.button-wrap { + display: flex; + justify-content: center; + margin-top: 24px; + padding: 0 24px; +} + +@media (prefers-color-scheme: dark) { + .main-title { + color: #000000e6; + } + + .sub-title, .bottom-text { + color: #00000099; + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/hmos-world/index.html b/products/phone/src/main/resources/resfile/bannercols/hmos-world/index.html new file mode 100644 index 0000000000000000000000000000000000000000..1372180656fb997a2921e827f50598a5609e2915 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/hmos-world/index.html @@ -0,0 +1,100 @@ + + + + + + + 开发者你好,欢迎来到HMOS世界 + + + + + + + + + +
+ +
+
开发者你好,欢迎来到HMOS世界
+
+ +
+
打造HarmonyOS应用开发助手
+
承载最新亮点特性
+
+
+
+ + +
+
+
+
+
开箱即用
+
UI组件和系统能力
+
+
+
能实时调整属性、样式、代码
+
并直接在手机上体验最终效果
+
+ + +
+ +
+
+
+
+
开发案例
+
无需编译 立即体验
+
+
+
通过动态加载技术,直接在HMOS
+
世界中体验Samples案例
+
+ + +
+ +
+
+
+
+
最新资讯
+
HarmonyOS最新亮点特性抢先看
+
+
+
实时传递HarmonyOS最新亮点特性助力开发者更高效、更便捷地开发出体验更佳的鸿蒙应用
+
+ + +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/banner.jpg b/products/phone/src/main/resources/resfile/bannercols/new-features/image/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..144381830cc3b1bb849c5160703ebc83d0673e4c Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/banner.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/black.jpg b/products/phone/src/main/resources/resfile/bannercols/new-features/image/black.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f33a0d6c9cc2c32a82fc31741fdd97ceaad74952 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/black.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/connection.gif b/products/phone/src/main/resources/resfile/bannercols/new-features/image/connection.gif new file mode 100644 index 0000000000000000000000000000000000000000..3f6ca70fedc44bd74b81b996557622215638fa9c Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/connection.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/delicate.gif b/products/phone/src/main/resources/resfile/bannercols/new-features/image/delicate.gif new file mode 100644 index 0000000000000000000000000000000000000000..7d944d05a0636f6b5f7e952442b497dc439939d8 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/delicate.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/intelligence.jpg b/products/phone/src/main/resources/resfile/bannercols/new-features/image/intelligence.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6c60345368606c973c68549ce28e89bf143439a0 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/intelligence.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/phone.png b/products/phone/src/main/resources/resfile/bannercols/new-features/image/phone.png new file mode 100644 index 0000000000000000000000000000000000000000..43ba8bbdeac2fb8b92e9bfe9388db6b2d637247a Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/phone.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/position.jpg b/products/phone/src/main/resources/resfile/bannercols/new-features/image/position.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9cd7d61c77c1865aa0bca28027150e31b7930f8f Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/position.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/secure.gif b/products/phone/src/main/resources/resfile/bannercols/new-features/image/secure.gif new file mode 100644 index 0000000000000000000000000000000000000000..d8c412580b4a7ad09f4daa38bb67348e3995ac6b Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/secure.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/simple-end.png b/products/phone/src/main/resources/resfile/bannercols/new-features/image/simple-end.png new file mode 100644 index 0000000000000000000000000000000000000000..f991271bb8aad6311382c891489664aaca25fa53 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/simple-end.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/simple-start.png b/products/phone/src/main/resources/resfile/bannercols/new-features/image/simple-start.png new file mode 100644 index 0000000000000000000000000000000000000000..83d319136266232194d137d5b3f9d08d4443ffff Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/simple-start.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/image/white.png b/products/phone/src/main/resources/resfile/bannercols/new-features/image/white.png new file mode 100644 index 0000000000000000000000000000000000000000..8281ea70b90ceb3c7a636aa50eb6b2360e506e5d Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/new-features/image/white.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/index.css b/products/phone/src/main/resources/resfile/bannercols/new-features/index.css new file mode 100644 index 0000000000000000000000000000000000000000..b1034eba8d7899ebaa0be46c4005b517c37494b0 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/new-features/index.css @@ -0,0 +1,212 @@ +/* body */ +.bg-black { + background-color: rgb(16, 16, 16); +} +.bg-white { + background-color: #e0dfdf; +} + +.section { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + position: relative; + width: 100%; + height: 648px; + overflow: hidden; +} + +.large-content { + height: 580px; +} + +.bg-img { + position: absolute; + top: 0; + height: 100%; +} + +.bg-container { + position: relative; + width: 100%; + height: 100%; + margin: 0 auto; + padding-top: 48px; + background-size: auto 100%; + background-position: center; + background-repeat: no-repeat; + z-index: 99; +} + +.title-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-weight: bold; +} + +.main-title { + font-size: 30px; + margin-bottom: 6px; +} + +.sub-title { + font-size: 20px; +} + +.bg-black .title-container .main-title, +.bg-black .title-container .sub-title { + background: linear-gradient( + to right, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 0.2) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.bg-white .title-container .main-title { + color: #303030; +} + +.bg-white .title-container .sub-title { + color: #5a5a5a; +} + +.phone-border { + position: absolute; + top: 163px; + left: 50%; + transform: translateX(-50%); + width: 360px; +} + +.phone-gif { + position: absolute; + top: 171px; + left: 50%; + transform: translateX(-50%); + width: 154px; + border-radius: 16px; +} + +.large-gif { + position: absolute; + top: 171px; + left: 50%; + transform: translateX(-50%) translateX(0.5px); + width: 352px; + border-radius: 16px; +} + +.phone-double { + position: absolute; + top: 171px; + left: 50%; + transform: translateX(-50%) translateX(-4px); + width: 179px; + height: 340px; +} + +.left-phone { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(-78px) translateY(10px); + width: 147px; + height: 307px; + border-radius: 16px; +} + +.left-border { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(-78px); + width: 339px; +} + +.right-phone { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(87px) translateY(10px); + width: 147px; + height: 307px; + border-radius: 16px; +} + +.right-border { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(86px); + width: 339px; +} + +.delicate-translate { + top: 165px; + transform: translateX(-50%) translateX(-10px); + width: 454px; +} + +.extra-translate { + width: 156px; + height: 328px; +} + +.safe-translate { + top: 175px; + width: 149px; +} + +.bottom-text { + position: absolute; + width: 100%; + bottom: 60px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + font-size: 14px; + line-height: 19px; + font-weight: 500; +} + +.bg-black .bottom-text { + color: rgba(255, 255, 255, 0.6); +} + +.bg-white .bottom-text { + color: #5a5a5a; +} + +@media (prefers-color-scheme: dark) { + .bg-black .title-container .main-title, + .bg-black .title-container .sub-title { + background: linear-gradient( + to right, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 0.2) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .bg-white .title-container .main-title { + color: #303030; + } + + .bg-white .title-container .sub-title { + color: #5a5a5a; + } + + .bg-black .bottom-text { + color: rgba(255, 255, 255, 0.6); + } + + .bg-white .bottom-text { + color: #5a5a5a; + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/new-features/index.html b/products/phone/src/main/resources/resfile/bannercols/new-features/index.html new file mode 100644 index 0000000000000000000000000000000000000000..a5515d0f174f013f70311ce25092accfc99839fc --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/new-features/index.html @@ -0,0 +1,141 @@ + + + + + + + HarmonyOS新特性介绍 + + + + + + + + +
+ +
+
HarmonyOS新特性介绍
+
+ +
+
打造HarmonyOS应用开发助手
+
承载最新亮点特性
+
+
+
+ + +
+
+
+
+
高端精致
+
和谐美学
+
+
+
文字清晰且易读,图标精确而清晰
+
色彩舒适而协调,动效流畅而生动
+
+ +
+ +
+
+
+
+
简单易用
+
打造简洁高效的系统架构
+
统一流畅的操作流程
+
+
+
为用户带来易感知、易理解、
+
易操作的极致体验
+
+
+ + + + + +
+
+ +
+
+
+
+
原生智能
+
随时随地  问问小艺
+
+
+
权威百科问答,知识搜索更靠谱
+
更多问题,问小艺
+
+ + +
+
+
+
+
+
原生安全
+
隐私安全,由你掌控
+
+
+
临时获取精准定位权限
+
隐私行为可知可控
+
+ + +
+ +
+
+
+
+
原生互联
+
不同设备,一个系统
+
+
+
原生互联,交互默契简单
+
应用无缝接续,内容自由分享
+
+ +
+ +
+
+
+ + + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/black.jpg b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/black.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f33a0d6c9cc2c32a82fc31741fdd97ceaad74952 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/black.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/common.gif b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/common.gif new file mode 100644 index 0000000000000000000000000000000000000000..3ef74b11c65456f1f0335163ed66774b2f1e79b0 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/common.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/component.gif b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/component.gif new file mode 100644 index 0000000000000000000000000000000000000000..01bca94cec5dfb46815897ad5a644e3b0199b5d1 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/component.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/experience.jpg b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/experience.jpg new file mode 100644 index 0000000000000000000000000000000000000000..620ca73f153e7322c9e89c4fe43deb42c15f4261 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/experience.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/oneshot.gif b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/oneshot.gif new file mode 100644 index 0000000000000000000000000000000000000000..ddd05b23015126c0066afb0641d4338d6a618221 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/oneshot.gif differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/phone.png b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/phone.png new file mode 100644 index 0000000000000000000000000000000000000000..43ba8bbdeac2fb8b92e9bfe9388db6b2d637247a Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/phone.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/white.png b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/white.png new file mode 100644 index 0000000000000000000000000000000000000000..8281ea70b90ceb3c7a636aa50eb6b2360e506e5d Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/image/white.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.css b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.css new file mode 100644 index 0000000000000000000000000000000000000000..1daed8d6f92cc5fa4e265c6033eb477ba75d199d --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.css @@ -0,0 +1,214 @@ +/* body */ +.bg-black { + background-color: rgb(16, 16, 16); +} +.bg-white { + background-color: #e0dfdf; +} + +.section { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + position: relative; + width: 100%; + height: 648px; + overflow: hidden; +} + +.bg-img { + position: absolute; + top: 0; + height: 100%; +} + +.bg-container { + position: relative; + width: 100%; + height: 100%; + margin: 0 auto; + padding-top: 48px; + background-size: auto 100%; + background-position: center; + background-repeat: no-repeat; + z-index: 99; +} + +.title-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-weight: bold; +} + +.main-title { + font-size: 30px; + margin-bottom: 6px; +} + +.sub-title { + font-size: 20px; +} + +.bg-black .title-container .main-title, +.bg-black .title-container .sub-title { + background: linear-gradient( + to right, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 0.2) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +.bg-white .title-container .main-title { + color: #303030; +} + +.bg-white .title-container .sub-title { + color: #5a5a5a; +} + +.phone-border { + position: absolute; + top: 163px; + left: 50%; + transform: translateX(-50%); + width: 360px; +} + +.phone-border-black { + transform: translateX(-50%) translateX(-3px); +} + +.phone-gif { + position: absolute; + top: 175px; + left: 50%; + transform: translateX(-50%); + width: 150px; + height: 323px; + border-radius: 16px; +} + +.phone-gif-black { + transform: translateX(-50%) translateX(-3px); +} + +.left-phone { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(-78px) translateY(4px); + width: 146px; + height: 320px; + border-radius: 16px; +} + +.left-border { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(-79px); + width: 179px; + height: 340px; +} + +.right-phone { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(87px) translateY(3px); + width: 146px; + height: 323px; + border-radius: 16px; +} + +.right-border { + position: absolute; + left: 50%; + transform: translateX(-50%) translateX(86px); + width: 179px; + height: 340px; +} + +.extra-translate { + top: 172px; + transform: translateX(-50%) translateX(-4.5px); + width: 147px; + height: 322px; + border-radius: 16px; +} + +.bottom-text { + position: absolute; + width: 100%; + bottom: 60px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 14px; + line-height: 19px; + font-weight: 500; +} + +.bg-black .bottom-text { + color: rgba(255, 255, 255, 0.6); +} + +.bg-white .bottom-text { + color: #5a5a5a; +} + +.button-wrap { + display: flex; + justify-content: center; + margin-top: 24px; + padding: 0 24px; +} + +.section-button { + padding: 0 16px; + height: 40px; + border-radius: 26px; + background-color: #0a59f7; + display: inline-block; + color: #f1f3f5; + text-align: center; + line-height: 40px; +} + +.section-button:active { + transform: scale(0.95); +} + +@media (prefers-color-scheme: dark) { + .bg-black .title-container .main-title, + .bg-black .title-container .sub-title { + background: linear-gradient( + to right, + rgba(255, 255, 255, 1) 50%, + rgba(255, 255, 255, 0.2) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + } + + .bg-white .title-container .main-title { + color: #303030; + } + + .bg-white .title-container .sub-title { + color: #5a5a5a; + } + + .bg-black .bottom-text { + color: rgba(255, 255, 255, 0.6); + } + + .bg-white .bottom-text { + color: #5a5a5a; + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.html b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c3049806f2266e01a481894122a498cb9aa2a607 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.html @@ -0,0 +1,90 @@ + + + + + + + ArkUI组件快速构建你的页面 + + + + + + + + +
+
+
使用ArkUI组件快速构建你的页面
+
+ +
+
ArkUI组件
+
打造应用的魔术师
+
+
+
+ + +
+
+
+
+
丰富多样
+
组件元素
+
+
+
ArkUI提供大量精心设计的组件
+
覆盖各种页面需求
+
+ + +
+ +
+
+
+
+
组件样式
+
打造简洁高效的系统架构
+
统一流畅的操作流程
+
+
+
ArkUI组件支持灵活的样式定制
+
体现应用的每个细节
+
+ + +
+ +
+
+
+
+
设计灵活
+
动画特效
+
+
+
ArkUI提供各种场景动画效果
+
增强用户体验和交互
+
+ + +
+ +
+
+
+
+
立即体验
+
+ + + + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.js b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.js new file mode 100644 index 0000000000000000000000000000000000000000..eb68954298665a32346b0025d65910effd9d09da --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/quick-build-with-arkui/index.js @@ -0,0 +1,47 @@ +import '../../articlecols/common/dist/jquery.js'; +function getArticleJson() { + return $.getJSON('../../articlecols/common/config/articleUrlConfig.json') + .done(function (data) { + return data; + }); +} +function getGiteeJson() { + return $.getJSON('../../articlecols/common/config/giteeUrlConfig.json') + .done(function (data) { + return data; + }); +} +const arrayA = Array.prototype.slice.call( + document.getElementsByClassName('section-button'), +); +Promise.all([getArticleJson(), getGiteeJson()]) + .then((res) => { + const articleUrlConfig = res[0]; + const giteeUrlConfig = res[1]; + arrayA.forEach((item) => { + let hrefValue = item.getAttribute('url'); + let type = 0; + if (!hrefValue) { + return; + } + if (hrefValue.includes('article')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (articleUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(articleUrlConfig[key], type); + }); + } else if (hrefValue.includes('gitee')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (giteeUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(giteeUrlConfig[key], type); + }); + } + }); + }); \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-start/image/banner.jpg b/products/phone/src/main/resources/resfile/bannercols/quick-start/image/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5a2ceebff5fa048e5c46aad868197a50b16c0006 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-start/image/banner.jpg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-start/image/right-arrow.png b/products/phone/src/main/resources/resfile/bannercols/quick-start/image/right-arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..390d16b7903491ac6bb0a3f4c8ea39dfeb07a7e7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-start/image/right-arrow.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-start/image/star_circle_fill.png b/products/phone/src/main/resources/resfile/bannercols/quick-start/image/star_circle_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e2b525d966f0569eddebd37478e22418b5b0bd Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/quick-start/image/star_circle_fill.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-start/index.css b/products/phone/src/main/resources/resfile/bannercols/quick-start/index.css new file mode 100644 index 0000000000000000000000000000000000000000..8475b833c802baccb507906655d1133d63681e20 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/quick-start/index.css @@ -0,0 +1,114 @@ +* { + color: rgba(0, 0, 0, 0.9); +} + +.header .sub-title { + margin: 5px 16px 0px; + font-size: 14px; + color: rgba(0, 0, 0, 0.6); + line-height: 16px; +} + +.banner-btn { + position: absolute; + margin: 0 auto; + padding: 0 16px; + bottom: 36px; + height: 40px; + border-radius: 20px; + background-color: rgba(255, 255, 255, 0.5); + text-align: center; + line-height: 40px; + color: #fff; + white-space: nowrap; + transition: background-color 0.1s ease, transform 0.1s ease; +} + +.banner-btn:active { + transform: scale(0.95); +} + +.section { + padding: 0px 16px; +} + +.list-card { + margin-top: 16px; + border-radius: 16px; + clip: auto; + background-color: #fff; + overflow: hidden; +} + +.list-card:last-child { + margin-bottom: 16px; +} + +.list-card-item { + position: relative; + display: flex; + font-size: 14px; + height: 48px; + line-height: 48px; + padding: 0 12px; +} + +.list-card-item:not(:nth-last-child(1))::after { + content: ''; + display: block; + position: absolute; + width: calc(100% - 24px); + bottom: 0; + border-bottom: 1px solid rgba(210, 210, 210, 0.5); +} + +.list-card-item:active { + background-color: #f8f8f8; +} + +.list-card-item span { + vertical-align: middle; +} + +.list-title { + display: flex; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} + +.list-title::before { + display: inline-block; + content: ''; + /* background-color: #000000e6; */ + background: url(./image/star_circle_fill.png) + no-repeat center center; + background-size: cover; + width: 18px; + height: 18px; + margin-right: 6px; + vertical-align: middle; + flex: none; +} + +.time-box { + float: right; + color: #0009; + display: flex; + align-items: center; + margin-left: 8px; +} + +.time-box::after { + display: inline-block; + content: ''; + /* background-color: #000000e6; */ + background: url(./image/right-arrow.png) + no-repeat center center; + background-size: cover; + width: 14px; + height: 14px; + vertical-align: middle; +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-start/index.html b/products/phone/src/main/resources/resfile/bannercols/quick-start/index.html new file mode 100644 index 0000000000000000000000000000000000000000..83ccf397ea148c4981a83a1d82f4051e2210c4d1 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/quick-start/index.html @@ -0,0 +1,148 @@ + + + + + + + 快速入门 + + + + + + +
+
+
快速入门
+
HarmonyOS应用开发快速入门
+
+ + +
+
+
+
+ HarmonyOS + ArkUI提供了丰富多样的UI组件,您可以使用这些组件轻松地编写出更加丰富漂亮的界面。 +
+ +
+ +
+

使用常见组件构建页面

+
+ 您将学习使用Text、Image等常用组件、Swiper、Grid、List、Scroll等可滑动组件,构造出第一个HarmonyOS页面。 +
+
+ + + + + +
+
+ +
+ +
+

应用架构设计基础

+
+ 通过架构设计,使您的应用更易于维护、扩展和测试。采用MVVM模式和分层架构设计,改造您的应用。 +
+
+ + +
+
+ +
+ +
+

构建更丰富的界面

+
+ 通过对Web界面的支持、以及更加丰富的数据结构与组件导航。实现更加丰富的界面显示。 +
+
+ + + +
+
+ +
+ +
+

HarmonyOS特性介绍

+
+ 基于HarmonyOS中的AI语音朗读让您的应用支持界面内的文本语音朗读,通过一次开发,多端部署的能力让您的应用可以在多个设备上运行,通过分布式流转能力让您的应用可以在您的各个设备之间进行无缝流转。 +
+
+ + + +
+
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/quick-start/index.js b/products/phone/src/main/resources/resfile/bannercols/quick-start/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3d5dd3882e0e4b5a508ef5b3fa8679bd50d8f77e --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/quick-start/index.js @@ -0,0 +1,47 @@ +import '../../articlecols/common/dist/jquery.js'; +function getArticleJson() { + return $.getJSON('../../articlecols/common/config/articleUrlConfig.json') + .done(function (data) { + return data; + }); +} +function getGiteeJson() { + return $.getJSON('../../articlecols/common/config/giteeUrlConfig.json') + .done(function (data) { + return data; + }); +} +const arrayA = Array.prototype.slice.call( + document.getElementsByClassName('jump-link'), +); +Promise.all([getArticleJson(), getGiteeJson()]) + .then((res) => { + const articleUrlConfig = res[0]; + const giteeUrlConfig = res[1]; + arrayA.forEach((item) => { + let hrefValue = item.getAttribute('href'); + let type = 0; + if (!hrefValue) { + return; + } + if (hrefValue.includes('article')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (articleUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(articleUrlConfig[key], type); + }); + } else if (hrefValue.includes('gitee')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (giteeUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(giteeUrlConfig[key], type); + }); + } + }); + }); diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/1_large.jpeg b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/1_large.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ccb8967a89a77310e747dd10a8452ab12a7caebb Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/1_large.jpeg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/2_large.jpeg b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/2_large.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5e044a457b32d21921d6ae1b97a6d270b6800c9d Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/2_large.jpeg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/3_large.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/3_large.png new file mode 100644 index 0000000000000000000000000000000000000000..c8d17c99b77688267bed3aff37b964108d366bf9 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/3_large.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/4_large.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/4_large.png new file mode 100644 index 0000000000000000000000000000000000000000..9864aaf62cedddc284fb40f0bc9600271b0286e0 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/4_large.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/5_large.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/5_large.png new file mode 100644 index 0000000000000000000000000000000000000000..04707a139cdf8270ed750cc777f53ce95ecf6618 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/5_large.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_1.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_1.png new file mode 100644 index 0000000000000000000000000000000000000000..f5d45624dde9d26a1a1780f257762d7eaa23550d Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_1.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_1_wide.jpeg b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_1_wide.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..bcb6657579dc0f7d168e26faf7cd95786ad48813 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_1_wide.jpeg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_2.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_2.png new file mode 100644 index 0000000000000000000000000000000000000000..4296385d12d021445af21ec69ca55de832208861 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_2.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_2_wide.jpeg b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_2_wide.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9e92a573f05c795fcae04641754d5e08d89354e0 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_2_wide.jpeg differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_3.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b5d1707bce48e0393964c9e008d9050f212c0c4d Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_3.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_3_wide.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_3_wide.png new file mode 100644 index 0000000000000000000000000000000000000000..77c343a216a1e87ef12c323e52cfdcd819e096f0 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_3_wide.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_4.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_4.png new file mode 100644 index 0000000000000000000000000000000000000000..42a3f79c561261af797d250bb9747c1d0bf9f1db Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_4.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_4_wide.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_4_wide.png new file mode 100644 index 0000000000000000000000000000000000000000..7bd615f5cf5712c67c18d8b7f327a904757ef906 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_4_wide.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_5.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_5.png new file mode 100644 index 0000000000000000000000000000000000000000..07786384ea9aa2dc26d4e9ae90404b83c0cb97bb Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_5.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_5_wide.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_5_wide.png new file mode 100644 index 0000000000000000000000000000000000000000..53628d48d99b08c243bd5ea86301e3c4be093519 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/card_bg_5_wide.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/close.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/close.png new file mode 100644 index 0000000000000000000000000000000000000000..bf6dd0933b58ff941e15cb49b6bebb21ba390136 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/close.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/design.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/design.png new file mode 100644 index 0000000000000000000000000000000000000000..ac07204a82b7c8b1081deb269774f381f39914b6 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/design.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/multi_device.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/multi_device.png new file mode 100644 index 0000000000000000000000000000000000000000..99f0a9cc38ab1dcb0d9f46241d0b96e5a6a5be36 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/multi_device.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/play.svg b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/play.svg new file mode 100644 index 0000000000000000000000000000000000000000..500662d294deb39ed24dcb6d605c7c7f830e8ecf --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/play.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/right_black.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/right_black.png new file mode 100644 index 0000000000000000000000000000000000000000..8e720afafacc4c9d976046dbcb20ce8ba1c88843 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/right_black.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/right_grey.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/right_grey.png new file mode 100644 index 0000000000000000000000000000000000000000..390d16b7903491ac6bb0a3f4c8ea39dfeb07a7e7 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/right_grey.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem1.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem1.png new file mode 100644 index 0000000000000000000000000000000000000000..ede991891f5b3b7f8d63f9d6aa82052294a766dc Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem1.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem2.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem2.png new file mode 100644 index 0000000000000000000000000000000000000000..f52ffec8d41342764e106906fc444283d7d6088b Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem2.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem4.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem4.png new file mode 100644 index 0000000000000000000000000000000000000000..cb0b3f36b299fdbacaf8546747b5e21dbca73e21 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem4.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem5.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem5.png new file mode 100644 index 0000000000000000000000000000000000000000..d893596b8941a8f7d65dfb7a132d92910a689116 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem5.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem6.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem6.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4383eaeb77e6b3928f350d7c7a41860e1f8640 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem6.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem8.png b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem8.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc6efdb31d3b88d6bd483fee2c3858b7d30c295 Binary files /dev/null and b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/image/swiperItem8.png differ diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.css b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.css new file mode 100644 index 0000000000000000000000000000000000000000..cf8985e7d05230f3d2fb8abdf8d1281d3a7976b7 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.css @@ -0,0 +1,394 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + user-select: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html { + margin-top: 88px; + background-color: #ffffff; +} + +ul { + list-style-type: none; + padding: 0; +} + +.main-content { + font-family: sans-serif; + color: #000000; +} + +a { + text-decoration: none; +} + +.chapter { + margin: 0 16px; + padding: 32px 0; + text-align: center; + background-color: #ffffff; +} + +.chapter .title { + margin: 0 auto; + font-size: 36px; + font-weight: bold; +} + +.chapter .desc { + margin: 16px auto 24px; + font-size: 14px; + line-height: 20px; + opacity: 0.6; +} + +@media screen and (min-width: 600px) { + .chapter { + margin: 0 10%; + } +} + +@media screen and (min-width: 850px) { + .chapter .desc { + margin: 12px auto 28px; + max-width: 65%; + font-size: max(16px, 1.25vw); + line-height: max(24px, 1.875vw); + } +} + +.jump-link { + display: inline-block; + margin: 8px; + min-width: 112px; + padding: 0 12px; + font-size: 14px; + font-weight: 700; + line-height: 32px; + border-radius: 32px; + color: #ffffff; + background-color: #000000; + text-align: center; + transition: background-color 0.2s ease, transform 0.2s ease; +} + +.jump-link:active { + transform: scale(0.95); +} + +/* header */ +.header .desc { + margin-top: 16px; +} + +.header .content { + position: relative; + width: 100%; + aspect-ratio: 860 / 328; + border-radius: 24px; + overflow: hidden; + font-size: 0; +} + +.play-button { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 4.219vw; + height: 4.219vw; + cursor: pointer; +} + +.close-button { + display: none; + position: absolute; + right: 20px; + top: 20px; + cursor: pointer; + z-index: 999; +} + +.header .content .content-img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.header .content video { + width: 100%; + display: none; + width: 100%; + height: 100%; + object-fit: cover; +} + +/* swiper */ +.swiper-wrap .desc { + margin-top: 8px; +} + +.swiper-wrap .swiper { + width: 100%; + position: relative; + text-align: center; + border-radius: 24px; + background: #f1f3f5; + overflow: hidden; +} + +.swiper-wrap .swiper .card-wrap { + width: 100%; + height: 100%; + display: flex; +} + +.swiper-wrap .swiper .card-wrap .card-item { + padding: 16px; + width: 100%; + border-radius: 24px; + background: #f1f3f5; + flex: 0 0 100%; +} + +.swiper-wrap .swiper .card-wrap .card-item .card-icon { + max-width: 100%; + margin-bottom: 16px; +} + +.swiper-wrap .swiper .card-wrap .card-item .card-title { + padding: 0 8px 8px 8px; + font-size: 26px; + line-height: 36px; + font-weight: bold; +} + +.swiper-wrap .swiper .card-wrap .card-item .card-desc { + padding: 0 8px; + font-size: 14px; + line-height: 20px; + opacity: 0.6; +} + +.swiper-wrap .swiper .swiper-pagination { + height: 32px; + width: 100%; + text-align: center; +} + +.swiper-wrap .swiper .swiper-pagination .swiper-point { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 5px; + margin: 0 3px; + background-color: rgba(0, 0, 0, 0.2); + transition: opacity 0.3s, background-color 0.3s, width 0.3s; + cursor: pointer; +} + +.swiper-point-selected { + width: 20px !important; + background-color: rgba(0, 0, 0, 1) !important; +} + +/* multi-device */ +.multi-device { + padding-bottom: 0; +} + +.multi-device img { + width: 100%; +} + +/* card-bg */ +.card-bg { + width: 100%; +} +.bg-mode { + position: relative; + width: 100%; + font-size: 0; +} + +.bg-mode .bg-mode-img { + max-width: 100%; + height: auto; + z-index: -1; +} + +.bg-mode .text-content { + position: absolute; + top: 0; + margin: 0 16px; + padding: 32px 0; + width: calc(100% - 32px); +} + +.bg-mode .font-color-white { + color: #ffffff; +} + +.bg-mode .text-content .title { + margin-bottom: 8px; + font-size: 36px; + line-height: 48px; + font-weight: bold; +} + +.bg-mode .text-content .desc { + font-size: 14px; + line-height: 20px; +} + +.bg-mode .text-content .jump-link { + margin: 16px 0 0; +} + +.bg-mode .text-content .link-group { + margin-top: 16px; + padding: 0; + white-space: nowrap; + overflow-x: auto; + scrollbar-width: none; +} + +.bg-mode .text-content .link-group-item { + display: inline-block; + margin: 8px 10px 8px 0px; + padding: 10px 16px; + font-size: 14px; + line-height: 16px; + color: rgba(0, 0, 0, 0.9); + background-color: #0000000c; + border-radius: 16px; +} + +.bg-mode .text-content .active { + color: #ffffff; + background-color: #0A59F7; +} + +.bg-mode .text-content .link-group-item:last-child { + margin-right: 0px; +} + +.media-display-sm { + display: flex; + justify-content: center; + width: 100%; + padding-bottom: 16px; +} +.media-display-lg { + display: none; +} + +@media screen and (max-width: 370px) { + .bg-mode .bg-mode-img-margin { + margin-top: 16px; + } +} + +@media screen and (min-width: 600px) { + .bg-mode:nth-child(1) > img { + width: 100%; + content: url(./image/card_bg_1_wide.jpeg); + } + + .bg-mode:nth-child(1) > .text-content, + .bg-mode:nth-child(3) > .text-content { + position: absolute; + margin-left: 6%; + top: 50%; + transform: translateY(-50%); + width: 40%; + } + .bg-mode:nth-child(2) > img { + width: 100%; + content: url(./image/card_bg_2_wide.jpeg); + } + + .bg-mode:nth-child(2) > .text-content { + position: absolute; + margin-left: 56%; + top: 50%; + transform: translateY(-50%); + width: 40%; + } + .bg-mode:nth-child(3) > img { + width: 100%; + } + + .media-display-sm { + display: none; + } + .media-display-lg { + display: inline-block; + } +} + +/* footer */ +.footer { + display: flex; + flex-direction: column; + align-items: center; + margin: 48px 24px 76px; + border-radius: 16px; + overflow: hidden; + background-color: #f1f3f5; + background-image: url(../common/image/f_dwen.png); + background-repeat: repeat; +} +.footerImg { + max-width: 328px; +} + +@media screen and (min-width: 600px) { + .footerImg { + max-width: calc(1.2 * 328px); + } +} +@media screen and (min-width: 900px) { + .footerImg { + max-width: calc(1.5 * 328px); + } +} +@media screen and (min-width: 1200px) { + .bg-mode:nth-child(1) > img { + content: url(./image/1_large.jpeg); + } + + .bg-mode:nth-child(2) > img { + content: url(./image/2_large.jpeg); + } +} + +@media (prefers-color-scheme: dark) { + .bg-mode .text-content .link-group, .bg-mode .text-content .title, .bg-mode .text-content .desc { + color: #000; + } + + .swiper-wrap .swiper .swiper-pagination .swiper-point { + background-color: rgba(255, 255, 255, 0.1); + } + + .swiper-point-selected { + background-color: rgba(255, 255, 255, 0.9) !important; + } + + .jump-link-dark { + color: #000; + background-color: rgba(255, 255, 255, 0.9); + } + + .media-display-sm { + background-color: #fff; + } + + .footer { + background-image: url(../common/image/f_dwen_dark.png); + } +} \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.html b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.html new file mode 100644 index 0000000000000000000000000000000000000000..25b64988a3488c150a49a2ca9c703b9a15328722 --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.html @@ -0,0 +1,164 @@ + + + + + + + HarmonyOS UX设计新体验介绍 + + + + +
+ +
+
HarmonyOS 新体验
+
+ HarmonyOS + 多设备设计指南与重点特性规范,为你提供面向垂类场景、全端侧的针对性设计建议。 + 并有简单易用的控件助力你打造高端精致的 HarmonyOS + 应用新体验,与你共同构建一个和谐的数字世界。 +
+
+ +
+
+ + +
+
HarmonyOS 特征设计指南
+
+ 提供HarmonyOS 完整设计规范,以及创新特性的适配规范,让你快速构建出 + HarmonyOS 全场景设备的创新体验。 +
+
+
+
+ +
新控件
+
+ 提供基于 HarmonyOS + 版本风格的控件样式及设计规范,帮助你快速了解控件设计和基础能力。 +
+
+
+ +
实况窗设计
+
+ 实况窗设计包含了卡片态、胶囊态、沉浸态,帮助用户聚焦进行中任务、方便快速查看和即时处理。 +
+
+
+ +
一键登录
+
+ 华为账号提供登录设计规范,保障 HarmonyOS + 应用拥有简单易用、高效一致、快速安全的登录体验。 +
+
+
+ +
元服务设计
+
+ 轻量高效、即点即用,多形态的组件构成样式为用户提供丰富便捷的应用服务。 +
+
+
+ +
多窗
+
+ 包含悬浮窗、分屏不同的窗口形态,为你提供灵活高效的多任务并行体验。 +
+
+
+ +
分享
+
+ 为各场景的内容分享体验提供设计规范,帮助你了解系统分享能力。 +
+
+
+
+ + + + + + +
+
+
+ + +
+
多设备响应式设计
+
+ HarmonyOS + 应用设计支持适配不同的屏幕尺寸和设备类型。保持多设备体验的连续性,降低你的工作量和维护成本。 +
+ + +
+ + +
+
+ +
+
应用UX体验标准
+
+ 本标准从影响用户体验的各个维度定义了相应测试规范,规定了应用需达到的基础体验要求,用于引导应用的设计与开发,以保证应用良好的使用体验。 +
+ 了解详情 +
+
+
+ +
+
多设备典型场景设计案例
+
+ 为你提供适合特征型场景的界面设计样式、方便你结合应用的业务场景,进行最佳界面适配和创新设计。 +
+ 了解详情 +
+
+
+ +
+
设计资源
+
+ 为你提供多种效率组件和界面模版,以及不断更新的设计资源库,包含图标、色彩、文字、音效等丰富的资源。 +
+ + 了解详情 +
+ +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.js b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.js new file mode 100644 index 0000000000000000000000000000000000000000..714224bdceea983ff706edb6447904950e8296cb --- /dev/null +++ b/products/phone/src/main/resources/resfile/bannercols/ux-design-new-experience/index.js @@ -0,0 +1,225 @@ +import '../../articlecols/common/dist/jquery.js'; +const swiper = document.getElementsByClassName('card-wrap')[0]; +const pointElements = document.getElementsByClassName('swiper-point'); + +let points = Array.from(pointElements); +let startX = 0; +let startY = 0; +let scrollX = 0; +let current = 0; +let scrollDir = 0; +let startTime = 0; +let maxDeltaX = 0; +let isDragging = false; + +function handleStart(e) { + const client = e.touches ? e.touches[0] : e; + startTime = Date.now(); + startX = client.clientX; + startY = client.clientY; + swiper.style.transition = 'none'; + + isDragging = true; + + // 鼠标事件动态绑定移动和结束监听 + if (!e.touches) { + document.addEventListener('mousemove', handleMove); + document.addEventListener('mouseup', handleEnd); + } +} + +function handleMove(e) { + // 只在拖动时处理 + if (!isDragging) { + return; + } + + const client = e.touches ? e.touches[0] : e; + const deltaX = client.clientX - startX; + const deltaY = client.clientY - startY; + + if (!scrollDir) { + scrollDir = Math.abs(deltaX) > Math.abs(deltaY) ? 'x' : 'y'; + } + + if (scrollDir === 'x') { + // 横向滚动阻止默认 + e.preventDefault(); + maxDeltaX = Math.abs(deltaX) > Math.abs(maxDeltaX) ? deltaX : maxDeltaX; + scrollX = client.clientX; + swiper.style.transform = `translateX(${current * -100 + }%) translateX(${deltaX}px)`; + } +} + +function handleEnd(e) { + if (!isDragging) { + return; + } + e.preventDefault(); + const endTime = Date.now(); + swiper.style.transition = 'transform 0.4s ease'; + + if (scrollDir === 'x') { + const delta = scrollX - startX; + const isFlick = endTime - startTime < 200; + const isReverseSwipe = Math.abs(maxDeltaX) - Math.abs(delta) > 20; + + if (isFlick) { + if (Math.abs(delta) > 10) { + updateCurrent(delta > 0 ? -1 : 1); + } + } else if (isReverseSwipe) { + // 取消滑动 + } else { + if (Math.abs(delta) > 50) { + updateCurrent(delta > 0 ? -1 : 1); + } + } + + swiper.style.transform = `translateX(${current * -100}%)`; + setPointSelect(current); + } + + // 重置状态 + scrollDir = undefined; + maxDeltaX = 0; + isDragging = false; + + // 移除鼠标监听 + if (!e.touches) { + document.removeEventListener('mousemove', handleMove); + document.removeEventListener('mouseup', handleEnd); + } +} + +function updateCurrent(step) { + const newIndex = current + step; + current = Math.max(0, Math.min(newIndex, swiper.children.length - 1)); +} + +swiper.addEventListener('touchstart', handleStart); +swiper.addEventListener('touchmove', handleMove); +swiper.addEventListener('touchend', handleEnd); + +swiper.addEventListener('mousedown', handleStart); + +points.forEach((item, index) => { + item.addEventListener('click', () => { + current = index; + // 重置swiper的位置以匹配动画开始前的位置(尽管这里可能看起来多余,但保持一致性是个好习惯) + swiper.style.transform = `translateX(${current * -100}%) translateX(0px)`; + // 设置过渡效果并触发动画 + swiper.style.transition = 'transform 0.4s ease'; + requestAnimationFrame(() => { + swiper.style.transform = `translateX(${current * -100}%)`; + }); + + setPointSelect(index); + }); +}); + +function setPointSelect(currentIndex) { + points.forEach((item, index) => { + item.classList.toggle('swiper-point-selected', index === currentIndex); + }); +} + +//liList +let liActive = 0; +const liList = Array.prototype.slice.call( + document.getElementsByClassName('link-group-item'), +); +const designImg = document.getElementById('bg_mode_img_design'); +const designImgListNormal = [ + './image/card_bg_3.png', + './image/card_bg_4.png', + './image/card_bg_5.png', +]; + +const designImgListWide = [ + './image/card_bg_3_wide.png', + './image/card_bg_4_wide.png', + './image/card_bg_5_wide.png', +]; + +const designImgListLarge = [ + './image/3_large.png', + './image/4_large.png', + './image/5_large.png', +]; + +liList.forEach((item, index) => { + item.addEventListener('click', () => { + liList[liActive].className = 'link-group-item'; + item.className = 'link-group-item active'; + liActive = index; + setBottomBg(); + }); +}); + +function setBottomBg() { + if (window.innerWidth < 600) { + designImg.src = designImgListNormal[liActive]; + } else if (window.innerWidth >= 600 && window.innerWidth < 1200) { + designImg.src = designImgListWide[liActive]; + } else { + designImg.src = designImgListLarge[liActive]; + } +} + +window.onload = () => { + setBottomBg(); +}; + +window.onresize = () => { + setBottomBg(); +}; + +function getArticleJson() { + return $.getJSON('../../articlecols/common/config/articleUrlConfig.json') + .done(function (data) { + return data; + }); +} +function getGiteeJson() { + return $.getJSON('../../articlecols/common/config/giteeUrlConfig.json') + .done(function (data) { + return data; + }); +} + +const arrayA = Array.prototype.slice.call( + document.getElementsByClassName('jump-link'), +); +Promise.all([getArticleJson(), getGiteeJson()]) + .then((res) => { + const articleUrlConfig = res[0]; + const giteeUrlConfig = res[1]; + arrayA.forEach((item) => { + let hrefValue = item.getAttribute('href'); + let type = 0; + if (!hrefValue) { + return; + } + if (hrefValue.includes('article')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (articleUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(articleUrlConfig[key], type); + }); + } else if (hrefValue.includes('gitee')) { + const key = hrefValue.split('_').slice(1).join('_'); + item.addEventListener('click', (event) => { + event.preventDefault(); + if (giteeUrlConfig[key].includes(articleUrlConfig.main_domain)) { + type = 1; + } + nativeActionData.webSheet(giteeUrlConfig[key], type); + }); + } + }); + }); \ No newline at end of file diff --git a/products/phone/src/main/resources/resfile/dist/common.js b/products/phone/src/main/resources/resfile/dist/common.js new file mode 100644 index 0000000000000000000000000000000000000000..029161456131809c3173464779ddf5ffd22ee439 --- /dev/null +++ b/products/phone/src/main/resources/resfile/dist/common.js @@ -0,0 +1,4 @@ +import{g as St}from"./common2.js";function ct(n){return n instanceof Map?n.clear=n.delete=n.set=function(){throw new Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=function(){throw new Error("set is read-only")}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach(e=>{const t=n[e],s=typeof t;(s==="object"||s==="function")&&!Object.isFrozen(t)&&ct(t)}),n}class Nn{constructor(e){e.data===void 0&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function lt(n){return n.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function ue(n,...e){const t=Object.create(null);for(const s in n)t[s]=n[s];return e.forEach(function(s){for(const d in s)t[d]=s[d]}),t}const vt="
",hn=n=>!!n.scope,Ot=(n,{prefix:e})=>{if(n.startsWith("language:"))return n.replace("language:","language-");if(n.includes(".")){const t=n.split(".");return[`${e}${t.shift()}`,...t.map((s,d)=>`${s}${"_".repeat(d+1)}`)].join(" ")}return`${e}${n}`};class wt{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=lt(e)}openNode(e){if(!hn(e))return;const t=Ot(e.scope,{prefix:this.classPrefix});this.span(t)}closeNode(e){hn(e)&&(this.buffer+=vt)}value(){return this.buffer}span(e){this.buffer+=``}}const yn=(n={})=>{const e={children:[]};return Object.assign(e,n),e};class _n{constructor(){this.rootNode=yn(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t=yn({scope:e});this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return typeof t=="string"?e.addText(t):t.children&&(e.openNode(t),t.children.forEach(s=>this._walk(e,s)),e.closeNode(t)),e}static _collapse(e){typeof e!="string"&&e.children&&(e.children.every(t=>typeof t=="string")?e.children=[e.children.join("")]:e.children.forEach(t=>{_n._collapse(t)}))}}class Rt extends _n{constructor(e){super(),this.options=e}addText(e){e!==""&&this.add(e)}startScope(e){this.openNode(e)}endScope(){this.closeNode()}__addSublanguage(e,t){const s=e.root;t&&(s.scope=`language:${t}`),this.add(s)}toHTML(){return new wt(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}}function he(n){return n?typeof n=="string"?n:n.source:null}function dt(n){return pe("(?=",n,")")}function At(n){return pe("(?:",n,")*")}function Mt(n){return pe("(?:",n,")?")}function pe(...n){return n.map(t=>he(t)).join("")}function Ct(n){const e=n[n.length-1];return typeof e=="object"&&e.constructor===Object?(n.splice(n.length-1,1),e):{}}function En(...n){return"("+(Ct(n).capture?"":"?:")+n.map(s=>he(s)).join("|")+")"}function ut(n){return new RegExp(n.toString()+"|").exec("").length-1}function It(n,e){const t=n&&n.exec(e);return t&&t.index===0}const kt=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function mn(n,{joinWith:e}){let t=0;return n.map(s=>{t+=1;const d=t;let o=he(s),i="";for(;o.length>0;){const r=kt.exec(o);if(!r){i+=o;break}i+=o.substring(0,r.index),o=o.substring(r.index+r[0].length),r[0][0]==="\\"&&r[1]?i+="\\"+String(Number(r[1])+d):(i+=r[0],r[0]==="("&&t++)}return i}).map(s=>`(${s})`).join(e)}const xt=/\b\B/,gt="[a-zA-Z]\\w*",fn="[a-zA-Z_]\\w*",bt="\\b\\d+(\\.\\d+)?",pt="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",_t="\\b(0b[01]+)",Dt="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",Lt=(n={})=>{const e=/^#![ ]*\//;return n.binary&&(n.begin=pe(e,/.*\b/,n.binary,/\b.*/)),ue({scope:"meta",begin:e,end:/$/,relevance:0,"on:begin":(t,s)=>{t.index!==0&&s.ignoreMatch()}},n)},ye={begin:"\\\\[\\s\\S]",relevance:0},Bt={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[ye]},Ut={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[ye]},Pt={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},Ae=function(n,e,t={}){const s=ue({scope:"comment",begin:n,end:e,contains:[]},t);s.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});const d=En("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return s.contains.push({begin:pe(/[ ]+/,"(",d,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s},Ft=Ae("//","$"),$t=Ae("/\\*","\\*/"),zt=Ae("#","$"),Kt={scope:"number",begin:bt,relevance:0},qt={scope:"number",begin:pt,relevance:0},Gt={scope:"number",begin:_t,relevance:0},Ht={scope:"regexp",begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[ye,{begin:/\[/,end:/\]/,relevance:0,contains:[ye]}]},Wt={scope:"title",begin:gt,relevance:0},Yt={scope:"title",begin:fn,relevance:0},Zt={begin:"\\.\\s*"+fn,relevance:0},Xt=function(n){return Object.assign(n,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})};var we=Object.freeze({__proto__:null,APOS_STRING_MODE:Bt,BACKSLASH_ESCAPE:ye,BINARY_NUMBER_MODE:Gt,BINARY_NUMBER_RE:_t,COMMENT:Ae,C_BLOCK_COMMENT_MODE:$t,C_LINE_COMMENT_MODE:Ft,C_NUMBER_MODE:qt,C_NUMBER_RE:pt,END_SAME_AS_BEGIN:Xt,HASH_COMMENT_MODE:zt,IDENT_RE:gt,MATCH_NOTHING_RE:xt,METHOD_GUARD:Zt,NUMBER_MODE:Kt,NUMBER_RE:bt,PHRASAL_WORDS_MODE:Pt,QUOTE_STRING_MODE:Ut,REGEXP_MODE:Ht,RE_STARTERS_RE:Dt,SHEBANG:Lt,TITLE_MODE:Wt,UNDERSCORE_IDENT_RE:fn,UNDERSCORE_TITLE_MODE:Yt});function Vt(n,e){n.input[n.index-1]==="."&&e.ignoreMatch()}function Qt(n,e){n.className!==void 0&&(n.scope=n.className,delete n.className)}function Jt(n,e){e&&n.beginKeywords&&(n.begin="\\b("+n.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",n.__beforeBegin=Vt,n.keywords=n.keywords||n.beginKeywords,delete n.beginKeywords,n.relevance===void 0&&(n.relevance=0))}function jt(n,e){Array.isArray(n.illegal)&&(n.illegal=En(...n.illegal))}function er(n,e){if(n.match){if(n.begin||n.end)throw new Error("begin & end are not supported with match");n.begin=n.match,delete n.match}}function nr(n,e){n.relevance===void 0&&(n.relevance=1)}const tr=(n,e)=>{if(!n.beforeMatch)return;if(n.starts)throw new Error("beforeMatch cannot be used with starts");const t=Object.assign({},n);Object.keys(n).forEach(s=>{delete n[s]}),n.keywords=t.keywords,n.begin=pe(t.beforeMatch,dt(t.begin)),n.starts={relevance:0,contains:[Object.assign(t,{endsParent:!0})]},n.relevance=0,delete t.beforeMatch},rr=["of","and","for","in","not","or","if","then","parent","list","value"],ar="keyword";function Et(n,e,t=ar){const s=Object.create(null);return typeof n=="string"?d(t,n.split(" ")):Array.isArray(n)?d(t,n):Object.keys(n).forEach(function(o){Object.assign(s,Et(n[o],e,o))}),s;function d(o,i){e&&(i=i.map(r=>r.toLowerCase())),i.forEach(function(r){const a=r.split("|");s[a[0]]=[o,ir(a[0],a[1])]})}}function ir(n,e){return e?Number(e):sr(n)?0:1}function sr(n){return rr.includes(n.toLowerCase())}const Tn={},be=n=>{console.error(n)},Sn=(n,...e)=>{console.log(`WARN: ${n}`,...e)},me=(n,e)=>{Tn[`${n}/${e}`]||(console.log(`Deprecated as of ${n}. ${e}`),Tn[`${n}/${e}`]=!0)},Re=new Error;function mt(n,e,{key:t}){let s=0;const d=n[t],o={},i={};for(let r=1;r<=e.length;r++)i[r+s]=d[r],o[r+s]=!0,s+=ut(e[r-1]);n[t]=i,n[t]._emit=o,n[t]._multi=!0}function or(n){if(Array.isArray(n.begin)){if(n.skip||n.excludeBegin||n.returnBegin)throw be("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),Re;if(typeof n.beginScope!="object"||n.beginScope===null)throw be("beginScope must be object"),Re;mt(n,n.begin,{key:"beginScope"}),n.begin=mn(n.begin,{joinWith:""})}}function cr(n){if(Array.isArray(n.end)){if(n.skip||n.excludeEnd||n.returnEnd)throw be("skip, excludeEnd, returnEnd not compatible with endScope: {}"),Re;if(typeof n.endScope!="object"||n.endScope===null)throw be("endScope must be object"),Re;mt(n,n.end,{key:"endScope"}),n.end=mn(n.end,{joinWith:""})}}function lr(n){n.scope&&typeof n.scope=="object"&&n.scope!==null&&(n.beginScope=n.scope,delete n.scope)}function dr(n){lr(n),typeof n.beginScope=="string"&&(n.beginScope={_wrap:n.beginScope}),typeof n.endScope=="string"&&(n.endScope={_wrap:n.endScope}),or(n),cr(n)}function ur(n){function e(i,r){return new RegExp(he(i),"m"+(n.case_insensitive?"i":"")+(n.unicodeRegex?"u":"")+(r?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(r,a){a.position=this.position++,this.matchIndexes[this.matchAt]=a,this.regexes.push([a,r]),this.matchAt+=ut(r)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);const r=this.regexes.map(a=>a[1]);this.matcherRe=e(mn(r,{joinWith:"|"}),!0),this.lastIndex=0}exec(r){this.matcherRe.lastIndex=this.lastIndex;const a=this.matcherRe.exec(r);if(!a)return null;const l=a.findIndex((g,b)=>b>0&&g!==void 0),c=this.matchIndexes[l];return a.splice(0,l),Object.assign(a,c)}}class s{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(r){if(this.multiRegexes[r])return this.multiRegexes[r];const a=new t;return this.rules.slice(r).forEach(([l,c])=>a.addRule(l,c)),a.compile(),this.multiRegexes[r]=a,a}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(r,a){this.rules.push([r,a]),a.type==="begin"&&this.count++}exec(r){const a=this.getMatcher(this.regexIndex);a.lastIndex=this.lastIndex;let l=a.exec(r);if(this.resumingScanAtSamePosition()&&!(l&&l.index===this.lastIndex)){const c=this.getMatcher(0);c.lastIndex=this.lastIndex+1,l=c.exec(r)}return l&&(this.regexIndex+=l.position+1,this.regexIndex===this.count&&this.considerAll()),l}}function d(i){const r=new s;return i.contains.forEach(a=>r.addRule(a.begin,{rule:a,type:"begin"})),i.terminatorEnd&&r.addRule(i.terminatorEnd,{type:"end"}),i.illegal&&r.addRule(i.illegal,{type:"illegal"}),r}function o(i,r){const a=i;if(i.isCompiled)return a;[Qt,er,dr,tr].forEach(c=>c(i,r)),n.compilerExtensions.forEach(c=>c(i,r)),i.__beforeBegin=null,[Jt,jt,nr].forEach(c=>c(i,r)),i.isCompiled=!0;let l=null;return typeof i.keywords=="object"&&i.keywords.$pattern&&(i.keywords=Object.assign({},i.keywords),l=i.keywords.$pattern,delete i.keywords.$pattern),l=l||/\w+/,i.keywords&&(i.keywords=Et(i.keywords,n.case_insensitive)),a.keywordPatternRe=e(l,!0),r&&(i.begin||(i.begin=/\B|\b/),a.beginRe=e(a.begin),!i.end&&!i.endsWithParent&&(i.end=/\B|\b/),i.end&&(a.endRe=e(a.end)),a.terminatorEnd=he(a.end)||"",i.endsWithParent&&r.terminatorEnd&&(a.terminatorEnd+=(i.end?"|":"")+r.terminatorEnd)),i.illegal&&(a.illegalRe=e(i.illegal)),i.contains||(i.contains=[]),i.contains=[].concat(...i.contains.map(function(c){return gr(c==="self"?i:c)})),i.contains.forEach(function(c){o(c,a)}),i.starts&&o(i.starts,r),a.matcher=d(a),a}if(n.compilerExtensions||(n.compilerExtensions=[]),n.contains&&n.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return n.classNameAliases=ue(n.classNameAliases||{}),o(n)}function ft(n){return n?n.endsWithParent||ft(n.starts):!1}function gr(n){return n.variants&&!n.cachedVariants&&(n.cachedVariants=n.variants.map(function(e){return ue(n,{variants:null},e)})),n.cachedVariants?n.cachedVariants:ft(n)?ue(n,{starts:n.starts?ue(n.starts):null}):Object.isFrozen(n)?ue(n):n}var br="11.9.0";class pr extends Error{constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}}const Ie=lt,vn=ue,On=Symbol("nomatch"),_r=7,Nt=function(n){const e=Object.create(null),t=Object.create(null),s=[];let d=!0;const o="Could not find the language '{}', did you forget to load/include a language module?",i={disableAutodetect:!0,name:"Plain text",contains:[]};let r={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:Rt};function a(u){return r.noHighlightRe.test(u)}function l(u){let f=u.className+" ";f+=u.parentNode?u.parentNode.className:"";const w=r.languageDetectRe.exec(f);if(w){const k=I(w[1]);return k||(Sn(o.replace("{}",w[1])),Sn("Falling back to no-highlight mode for this block.",u)),k?w[1]:"no-highlight"}return f.split(/\s+/).find(k=>a(k)||I(k))}function c(u,f,w){let k="",U="";typeof f=="object"?(k=u,w=f.ignoreIllegals,U=f.language):(me("10.7.0","highlight(lang, code, ...args) has been deprecated."),me("10.7.0",`Please use highlight(code, options) instead. +https://github.com/highlightjs/highlight.js/issues/2277`),U=u,k=f),w===void 0&&(w=!0);const Y={code:k,language:U};B("before:highlight",Y);const ne=Y.result?Y.result:g(Y.language,Y.code,w);return ne.code=Y.code,B("after:highlight",ne),ne}function g(u,f,w,k){const U=Object.create(null);function Y(_,y){return _.keywords[y]}function ne(){if(!S.keywords){G.addText(P);return}let _=0;S.keywordPatternRe.lastIndex=0;let y=S.keywordPatternRe.exec(P),v="";for(;y;){v+=P.substring(_,y.index);const x=re.case_insensitive?y[0].toLowerCase():y[0],X=Y(S,x);if(X){const[se,Me]=X;if(G.addText(v),v="",U[x]=(U[x]||0)+1,U[x]<=_r&&(Ee+=Me),se.startsWith("_"))v+=y[0];else{const Ce=re.classNameAliases[se]||se;Z(y[0],Ce)}}else v+=y[0];_=S.keywordPatternRe.lastIndex,y=S.keywordPatternRe.exec(P)}v+=P.substring(_),G.addText(v)}function te(){if(P==="")return;let _=null;if(typeof S.subLanguage=="string"){if(!e[S.subLanguage]){G.addText(P);return}_=g(S.subLanguage,P,!0,ve[S.subLanguage]),ve[S.subLanguage]=_._top}else _=p(P,S.subLanguage.length?S.subLanguage:null);S.relevance>0&&(Ee+=_.relevance),G.__addSublanguage(_._emitter,_.language)}function z(){S.subLanguage!=null?te():ne(),P=""}function Z(_,y){_!==""&&(G.startScope(y),G.addText(_),G.endScope())}function q(_,y){let v=1;const x=y.length-1;for(;v<=x;){if(!_._emit[v]){v++;continue}const X=re.classNameAliases[_[v]]||_[v],se=y[v];X?Z(se,X):(P=se,ne(),P=""),v++}}function K(_,y){return _.scope&&typeof _.scope=="string"&&G.openNode(re.classNameAliases[_.scope]||_.scope),_.beginScope&&(_.beginScope._wrap?(Z(P,re.classNameAliases[_.beginScope._wrap]||_.beginScope._wrap),P=""):_.beginScope._multi&&(q(_.beginScope,y),P="")),S=Object.create(_,{parent:{value:S}}),S}function Q(_,y,v){let x=It(_.endRe,v);if(x){if(_["on:end"]){const X=new Nn(_);_["on:end"](y,X),X.isMatchIgnored&&(x=!1)}if(x){for(;_.endsParent&&_.parent;)_=_.parent;return _}}if(_.endsWithParent)return Q(_.parent,y,v)}function j(_){return S.matcher.regexIndex===0?(P+=_[0],1):(ce=!0,0)}function ee(_){const y=_[0],v=_.rule,x=new Nn(v),X=[v.__beforeBegin,v["on:begin"]];for(const se of X)if(se&&(se(_,x),x.isMatchIgnored))return j(y);return v.skip?P+=y:(v.excludeBegin&&(P+=y),z(),!v.returnBegin&&!v.excludeBegin&&(P=y)),K(v,_),v.returnBegin?0:y.length}function ie(_){const y=_[0],v=f.substring(_.index),x=Q(S,_,v);if(!x)return On;const X=S;S.endScope&&S.endScope._wrap?(z(),Z(y,S.endScope._wrap)):S.endScope&&S.endScope._multi?(z(),q(S.endScope,_)):X.skip?P+=y:(X.returnEnd||X.excludeEnd||(P+=y),z(),X.excludeEnd&&(P=y));do S.scope&&G.closeNode(),!S.skip&&!S.subLanguage&&(Ee+=S.relevance),S=S.parent;while(S!==x.parent);return x.starts&&K(x.starts,_),X.returnEnd?0:y.length}function oe(){const _=[];for(let y=S;y!==re;y=y.parent)y.scope&&_.unshift(y.scope);_.forEach(y=>G.openNode(y))}let ge={};function Te(_,y){const v=y&&y[0];if(P+=_,v==null)return z(),0;if(ge.type==="begin"&&y.type==="end"&&ge.index===y.index&&v===""){if(P+=f.slice(y.index,y.index+1),!d){const x=new Error(`0 width match regex (${u})`);throw x.languageName=u,x.badRule=ge.rule,x}return 1}if(ge=y,y.type==="begin")return ee(y);if(y.type==="illegal"&&!w){const x=new Error('Illegal lexeme "'+v+'" for mode "'+(S.scope||"")+'"');throw x.mode=S,x}else if(y.type==="end"){const x=ie(y);if(x!==On)return x}if(y.type==="illegal"&&v==="")return 1;if(de>1e5&&de>y.index*3)throw new Error("potential infinite loop, way more iterations than matches");return P+=v,v.length}const re=I(u);if(!re)throw be(o.replace("{}",u)),new Error('Unknown language: "'+u+'"');const Se=ur(re);let _e="",S=k||Se;const ve={},G=new r.__emitter(r);oe();let P="",Ee=0,le=0,de=0,ce=!1;try{if(re.__emitTokens)re.__emitTokens(f,G);else{for(S.matcher.considerAll();;){de++,ce?ce=!1:S.matcher.considerAll(),S.matcher.lastIndex=le;const _=S.matcher.exec(f);if(!_)break;const y=f.substring(le,_.index),v=Te(y,_);le=_.index+v}Te(f.substring(le))}return G.finalize(),_e=G.toHTML(),{language:u,value:_e,relevance:Ee,illegal:!1,_emitter:G,_top:S}}catch(_){if(_.message&&_.message.includes("Illegal"))return{language:u,value:Ie(f),illegal:!0,relevance:0,_illegalBy:{message:_.message,index:le,context:f.slice(le-100,le+100),mode:_.mode,resultSoFar:_e},_emitter:G};if(d)return{language:u,value:Ie(f),illegal:!1,relevance:0,errorRaised:_,_emitter:G,_top:S};throw _}}function b(u){const f={value:Ie(u),illegal:!1,relevance:0,_top:i,_emitter:new r.__emitter(r)};return f._emitter.addText(u),f}function p(u,f){f=f||r.languages||Object.keys(e);const w=b(u),k=f.filter(I).filter(F).map(z=>g(z,u,!1));k.unshift(w);const U=k.sort((z,Z)=>{if(z.relevance!==Z.relevance)return Z.relevance-z.relevance;if(z.language&&Z.language){if(I(z.language).supersetOf===Z.language)return 1;if(I(Z.language).supersetOf===z.language)return-1}return 0}),[Y,ne]=U,te=Y;return te.secondBest=ne,te}function E(u,f,w){const k=f&&t[f]||w;u.classList.add("hljs"),u.classList.add(`language-${k}`)}function m(u){let f=null;const w=l(u);if(a(w))return;if(B("before:highlightElement",{el:u,language:w}),u.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",u);return}if(u.children.length>0&&(r.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(u)),r.throwUnescapedHTML))throw new pr("One of your code blocks includes unescaped HTML.",u.innerHTML);f=u;const k=f.textContent,U=w?c(k,{language:w,ignoreIllegals:!0}):p(k);u.innerHTML=U.value,u.dataset.highlighted="yes",E(u,w,U.language),u.result={language:U.language,re:U.relevance,relevance:U.relevance},U.secondBest&&(u.secondBest={language:U.secondBest.language,relevance:U.secondBest.relevance}),B("after:highlightElement",{el:u,result:U,text:k})}function h(u){r=vn(r,u)}const N=()=>{M(),me("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function O(){M(),me("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let C=!1;function M(){if(document.readyState==="loading"){C=!0;return}document.querySelectorAll(r.cssSelector).forEach(m)}function A(){C&&M()}typeof window<"u"&&window.addEventListener&&window.addEventListener("DOMContentLoaded",A,!1);function D(u,f){let w=null;try{w=f(n)}catch(k){if(be("Language definition for '{}' could not be registered.".replace("{}",u)),d)be(k);else throw k;w=i}w.name||(w.name=u),e[u]=w,w.rawDefinition=f.bind(null,n),w.aliases&&H(w.aliases,{languageName:u})}function L(u){delete e[u];for(const f of Object.keys(t))t[f]===u&&delete t[f]}function T(){return Object.keys(e)}function I(u){return u=(u||"").toLowerCase(),e[u]||e[t[u]]}function H(u,{languageName:f}){typeof u=="string"&&(u=[u]),u.forEach(w=>{t[w.toLowerCase()]=f})}function F(u){const f=I(u);return f&&!f.disableAutodetect}function J(u){u["before:highlightBlock"]&&!u["before:highlightElement"]&&(u["before:highlightElement"]=f=>{u["before:highlightBlock"](Object.assign({block:f.el},f))}),u["after:highlightBlock"]&&!u["after:highlightElement"]&&(u["after:highlightElement"]=f=>{u["after:highlightBlock"](Object.assign({block:f.el},f))})}function ae(u){J(u),s.push(u)}function W(u){const f=s.indexOf(u);f!==-1&&s.splice(f,1)}function B(u,f){const w=u;s.forEach(function(k){k[w]&&k[w](f)})}function V(u){return me("10.7.0","highlightBlock will be removed entirely in v12.0"),me("10.7.0","Please use highlightElement now."),m(u)}Object.assign(n,{highlight:c,highlightAuto:p,highlightAll:M,highlightElement:m,highlightBlock:V,configure:h,initHighlighting:N,initHighlightingOnLoad:O,registerLanguage:D,unregisterLanguage:L,listLanguages:T,getLanguage:I,registerAliases:H,autoDetection:F,inherit:vn,addPlugin:ae,removePlugin:W}),n.debugMode=function(){d=!1},n.safeMode=function(){d=!0},n.versionString=br,n.regex={concat:pe,lookahead:dt,either:En,optional:Mt,anyNumberOfTimes:At};for(const u in we)typeof we[u]=="object"&&ct(we[u]);return Object.assign(n,we),n},fe=Nt({});fe.newInstance=()=>Nt({});var Er=fe;fe.HighlightJS=fe;fe.default=fe;var ke,wn;function mr(){if(wn)return ke;wn=1;function n(e){const t=e.regex,s=t.concat(/[\p{L}_]/u,t.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),d=/[\p{L}0-9._:-]+/u,o={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/,contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},r=e.inherit(i,{begin:/\(/,end:/\)/}),a=e.inherit(e.APOS_STRING_MODE,{className:"string"}),l=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),c={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,l,a,r,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[i,r,l,a]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},o,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/,relevance:10,contains:[l]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[c],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[c],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:t.concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:s,relevance:0,starts:c}]},{className:"tag",begin:t.concat(/<\//,t.lookahead(t.concat(s,/>/))),contains:[{className:"name",begin:s,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}return ke=n,ke}var xe,Rn;function fr(){if(Rn)return xe;Rn=1;function n(e){const t=e.regex,s={},d={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[s]}]};Object.assign(s,{className:"variable",variants:[{begin:t.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},d]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},i={begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},r={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,o]};o.contains.push(r);const a={match:/\\"/},l={className:"string",begin:/'/,end:/'/},c={match:/\\'/},g={begin:/\$?\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},b=["fish","bash","zsh","sh","csh","ksh","tcsh","dash","scsh"],p=e.SHEBANG({binary:`(${b.join("|")})`,relevance:10}),E={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},m=["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"],h=["true","false"],N={match:/(\/[a-z._-]+)+/},O=["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset"],C=["alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias"],M=["autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp"],A=["chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"];return{name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/,keyword:m,literal:h,built_in:[...O,...C,"set","shopt",...M,...A]},contains:[p,e.SHEBANG(),E,g,e.HASH_COMMENT_MODE,i,N,r,a,l,c,s]}}return xe=n,xe}var De,An;function Nr(){if(An)return De;An=1;function n(e){const t=e.regex,s=e.COMMENT("//","$",{contains:[{begin:/\\\n/}]}),d="decltype\\(auto\\)",o="[a-zA-Z_]\\w*::",r="("+d+"|"+t.optional(o)+"[a-zA-Z_]\\w*"+t.optional("<[^<>]+>")+")",a={className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{match:/\batomic_[a-z]{3,6}\b/}]},c={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'("+"\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)"+"|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},g={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},b={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(c,{className:"string"}),{className:"string",begin:/<.*?>/},s,e.C_BLOCK_COMMENT_MODE]},p={className:"title",begin:t.optional(o)+e.IDENT_RE,relevance:0},E=t.optional(o)+e.IDENT_RE+"\\s*\\(",N={keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"],type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"],literal:"true false NULL",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr"},O=[b,a,s,e.C_BLOCK_COMMENT_MODE,g,c],C={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:N,contains:O.concat([{begin:/\(/,end:/\)/,keywords:N,contains:O.concat(["self"]),relevance:0}]),relevance:0},M={begin:"("+r+"[\\*&\\s]+)+"+E,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:N,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:d,keywords:N,relevance:0},{begin:E,returnBegin:!0,contains:[e.inherit(p,{className:"title.function"})],relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/,keywords:N,relevance:0,contains:[s,e.C_BLOCK_COMMENT_MODE,c,g,a,{begin:/\(/,end:/\)/,keywords:N,relevance:0,contains:["self",s,e.C_BLOCK_COMMENT_MODE,c,g,a]}]},a,s,e.C_BLOCK_COMMENT_MODE,b]};return{name:"C",aliases:["h"],keywords:N,disableAutodetect:!0,illegal:"=]/,contains:[{beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:b,strings:c,keywords:N}}}return De=n,De}var Le,Mn;function hr(){if(Mn)return Le;Mn=1;function n(e){const t=e.regex,s=e.COMMENT("//","$",{contains:[{begin:/\\\n/}]}),d="decltype\\(auto\\)",o="[a-zA-Z_]\\w*::",r="(?!struct)("+d+"|"+t.optional(o)+"[a-zA-Z_]\\w*"+t.optional("<[^<>]+>")+")",a={className:"type",begin:"\\b[a-z\\d_]*_t\\b"},c={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'("+"\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)"+"|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},g={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},b={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(c,{className:"string"}),{className:"string",begin:/<.*?>/},s,e.C_BLOCK_COMMENT_MODE]},p={className:"title",begin:t.optional(o)+e.IDENT_RE,relevance:0},E=t.optional(o)+e.IDENT_RE+"\\s*\\(",m=["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"],h=["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"],N=["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"],O=["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"],A={type:h,keyword:m,literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"],_type_hints:N},D={className:"function.dispatch",relevance:0,keywords:{_hint:O},begin:t.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,t.lookahead(/(<[^<>]+>|)\s*\(/))},L=[D,b,a,s,e.C_BLOCK_COMMENT_MODE,g,c],T={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:A,contains:L.concat([{begin:/\(/,end:/\)/,keywords:A,contains:L.concat(["self"]),relevance:0}]),relevance:0},I={className:"function",begin:"("+r+"[\\*&\\s]+)+"+E,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:A,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:d,keywords:A,relevance:0},{begin:E,returnBegin:!0,contains:[p],relevance:0},{begin:/::/,relevance:0},{begin:/:/,endsWithParent:!0,contains:[c,g]},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/,keywords:A,relevance:0,contains:[s,e.C_BLOCK_COMMENT_MODE,c,g,a,{begin:/\(/,end:/\)/,keywords:A,relevance:0,contains:["self",s,e.C_BLOCK_COMMENT_MODE,c,g,a]}]},a,s,e.C_BLOCK_COMMENT_MODE,b]};return{name:"C++",aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:A,illegal:"",keywords:A,contains:["self",a]},{begin:e.IDENT_RE+"::",keywords:A},{match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/],className:{1:"keyword",3:"title.class"}}])}}return Le=n,Le}var Be,Cn;function yr(){if(Cn)return Be;Cn=1;function n(e){const t=["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"],s=["public","private","protected","static","internal","protected","abstract","async","extern","override","unsafe","virtual","new","sealed","partial"],d=["default","false","null","true"],o=["abstract","as","base","break","case","catch","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","scoped","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"],i=["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"],r={keyword:o.concat(i),built_in:t,literal:d},a=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),l={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},g=e.inherit(c,{illegal:/\n/}),b={className:"subst",begin:/\{/,end:/\}/,keywords:r},p=e.inherit(b,{illegal:/\n/}),E={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},e.BACKSLASH_ESCAPE,p]},m={className:"string",begin:/\$@"/,end:'"',contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},b]},h=e.inherit(m,{illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},p]});b.contains=[m,E,c,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,l,e.C_BLOCK_COMMENT_MODE],p.contains=[h,E,g,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,l,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];const N={variants:[m,E,c,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},O={begin:"<",end:">",contains:[{beginKeywords:"in out"},a]},C=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",M={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:r,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:""},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{keyword:"if else elif endif define undef warning error line region endregion pragma checksum"}},N,l,{beginKeywords:"class interface",relevance:0,end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},a,O,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[a,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[a,O,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+C+"\\s+)+"+e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:r,contains:[{beginKeywords:s.join(" "),relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0,contains:[e.TITLE_MODE,O],relevance:0},{match:/\(\)/},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:r,relevance:0,contains:[N,l,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},M]}}return Be=n,Be}var Ue,In;function Tr(){if(In)return Ue;In=1;const n=r=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:r.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:r.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],s=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],d=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse();function i(r){const a=r.regex,l=n(r),c={begin:/-(webkit|moz|ms|o)-(?=[a-z])/},g="and or not only",b=/@-?\w[\w]*(-\w+)*/,p="[a-zA-Z-][a-zA-Z0-9_-]*",E=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE];return{name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"},contains:[l.BLOCK_COMMENT,c,l.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0},{className:"selector-class",begin:"\\."+p,relevance:0},l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{begin:":("+s.join("|")+")"},{begin:":(:)?("+d.join("|")+")"}]},l.CSS_VARIABLE,{className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:/:/,end:/[;}{]/,contains:[l.BLOCK_COMMENT,l.HEXCOLOR,l.IMPORTANT,l.CSS_NUMBER_MODE,...E,{begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri"},contains:[...E,{className:"string",begin:/[^)]/,endsWithParent:!0,excludeEnd:!0}]},l.FUNCTION_DISPATCH]},{begin:a.lookahead(/@/),end:"[{;]",relevance:0,illegal:/:/,contains:[{className:"keyword",begin:b},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{$pattern:/[a-z-]+/,keyword:g,attribute:t.join(" ")},contains:[{begin:/[a-z-]+(?=:)/,className:"attribute"},...E,l.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"\\b("+e.join("|")+")\\b"}]}}return Ue=i,Ue}var Pe,kn;function Sr(){if(kn)return Pe;kn=1;function n(e){const t=e.regex,s={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},d={begin:"^[-\\*]{3,}",end:"$"},o={className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},i={className:"bullet",begin:"^[ ]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},r={begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]},a=/[A-Za-z][A-Za-z0-9+.-]*/,l={variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/,relevance:2},{begin:t.concat(/\[.+?\]\(/,a,/:\/\/.*?\)/),relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}]},c={className:"strong",contains:[],variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}]},g={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{begin:/_(?![_\s])/,end:/_/,relevance:0}]},b=e.inherit(c,{contains:[]}),p=e.inherit(g,{contains:[]});c.contains.push(p),g.contains.push(b);let E=[s,l];return[c,g,b,p].forEach(N=>{N.contains=N.contains.concat(E)}),E=E.concat(c,g),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:E},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:E}]}]},s,i,c,g,{className:"quote",begin:"^>\\s+",contains:E,end:"$"},o,d,l,r]}}return Pe=n,Pe}var Fe,xn;function vr(){if(xn)return Fe;xn=1;function n(e){const t=e.regex;return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,match:t.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/)},{className:"comment",variants:[{begin:t.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/),end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/,end:/$/}]}}return Fe=n,Fe}var $e,Dn;function Or(){if(Dn)return $e;Dn=1;function n(e){const t=e.regex,s="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",d=t.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),o=t.concat(d,/(::\w+)*/),r={"variable.constant":["__FILE__","__LINE__","__ENCODING__"],"variable.language":["self","super"],keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield",...["include","extend","prepend","public","private","protected","raise","throw"]],built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"],literal:["true","false","nil"]},a={className:"doctag",begin:"@[A-Za-z]+"},l={begin:"#<",end:">"},c=[e.COMMENT("#","$",{contains:[a]}),e.COMMENT("^=begin","^=end",{contains:[a],relevance:10}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],g={className:"subst",begin:/#\{/,end:/\}/,keywords:r},b={className:"string",contains:[e.BACKSLASH_ESCAPE,g],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{begin:t.concat(/<<[-~]?'?/,t.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,g]})]}]},p="[1-9](_?[0-9])*|0",E="[0-9](_?[0-9])*",m={className:"number",relevance:0,variants:[{begin:`\\b(${p})(\\.(${E}))?([eE][+-]?(${E})|r)?i?\\b`},{begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{begin:"\\b0(_?[0-7])+r?i?\\b"}]},h={variants:[{match:/\(\)/},{className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,keywords:r}]},L=[b,{variants:[{match:[/class\s+/,o,/\s+<\s+/,o]},{match:[/\b(class|module)\s+/,o]}],scope:{2:"title.class",4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,o],scope:{2:"title.class"},keywords:r},{relevance:0,match:[o,/\.new[. (]/],scope:{1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"},{relevance:0,match:d,scope:"title.class"},{match:[/def/,/\s+/,s],scope:{1:"keyword",3:"title.function"},contains:[h]},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[b,{begin:s}],relevance:0},m,{className:"variable",begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,g],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(l,c),relevance:0}].concat(l,c);g.contains=L,h.contains=L;const F=[{begin:/^\s*=>/,starts:{end:"$",contains:L}},{className:"meta.prompt",begin:"^("+"[>?]>"+"|"+"[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]"+"|"+"(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>"+")(?=[ ])",starts:{end:"$",keywords:r,contains:L}}];return c.unshift(l),{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/,contains:[e.SHEBANG({binary:"ruby"})].concat(F).concat(c).concat(L)}}return $e=n,$e}var ze,Ln;function wr(){if(Ln)return ze;Ln=1;function n(e){const i={keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"],type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"],literal:["true","false","iota","nil"],built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"]};return{name:"Go",aliases:["golang"],keywords:i,illegal:"d(i,r,a-1))}function o(i){const r=i.regex,a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",l=a+d("(?:<"+a+"~~~(?:\\s*,\\s*"+a+"~~~)*>)?",/~~~/g,2),E={keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed","yield","permits"],literal:["false","true","null"],type:["char","boolean","long","float","int","byte","short","double"],built_in:["super","this"]},m={className:"meta",begin:"@"+a,contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},h={className:"params",begin:/\(/,end:/\)/,keywords:E,relevance:0,contains:[i.C_BLOCK_COMMENT_MODE],endsParent:!0};return{name:"Java",aliases:["jsp"],keywords:E,illegal:/<\/|#/,contains:[i.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{begin:/import java\.[a-z]+\./,keywords:"import",relevance:2},i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/,className:"string",contains:[i.BACKSLASH_ESCAPE]},i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,{match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,a],className:{1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{begin:[r.concat(/(?!else)/,a),/\s+/,a,/\s+/,/=(?!=)/],className:{1:"type",3:"variable",5:"operator"}},{begin:[/record/,/\s+/,a],className:{1:"keyword",3:"title.class"},contains:[h,i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE]},{beginKeywords:"new throw return else",relevance:0},{begin:["(?:"+l+"\\s+)",i.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{2:"title.function"},keywords:E,contains:[{className:"params",begin:/\(/,end:/\)/,keywords:E,relevance:0,contains:[m,i.APOS_STRING_MODE,i.QUOTE_STRING_MODE,s,i.C_BLOCK_COMMENT_MODE]},i.C_LINE_COMMENT_MODE,i.C_BLOCK_COMMENT_MODE]},s,m]}}return Ge=o,Ge}var He,Fn;function Cr(){if(Fn)return He;Fn=1;const n="[A-Za-z$_][0-9A-Za-z$_]*",e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],t=["true","false","null","undefined","NaN","Infinity"],s=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],d=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],o=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],i=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],r=[].concat(o,s,d);function a(l){const c=l.regex,g=(q,{after:K})=>{const Q="",end:""},E=/<[A-Za-z0-9\\._:-]+\s*\/>/,m={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(q,K)=>{const Q=q[0].length+q.index,j=q.input[Q];if(j==="<"||j===","){K.ignoreMatch();return}j===">"&&(g(q,{after:Q})||K.ignoreMatch());let ee;const ie=q.input.substring(Q);if(ee=ie.match(/^\s*=/)){K.ignoreMatch();return}if((ee=ie.match(/^\s+extends\s+/))&&ee.index===0){K.ignoreMatch();return}}},h={$pattern:n,keyword:e,literal:t,built_in:r,"variable.language":i},N="[0-9](_?[0-9])*",O=`\\.(${N})`,C="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",M={className:"number",variants:[{begin:`(\\b(${C})((${O})|\\.)?|(${O}))[eE][+-]?(${N})\\b`},{begin:`\\b(${C})\\b((${O})\\b|\\.)?|(${O})\\b`},{begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{begin:"\\b0[0-7]+n?\\b"}],relevance:0},A={className:"subst",begin:"\\$\\{",end:"\\}",keywords:h,contains:[]},D={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[l.BACKSLASH_ESCAPE,A],subLanguage:"xml"}},L={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[l.BACKSLASH_ESCAPE,A],subLanguage:"css"}},T={begin:"gql`",end:"",starts:{end:"`",returnEnd:!1,contains:[l.BACKSLASH_ESCAPE,A],subLanguage:"graphql"}},I={className:"string",begin:"`",end:"`",contains:[l.BACKSLASH_ESCAPE,A]},F={className:"comment",variants:[l.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,excludeBegin:!0,relevance:0},{className:"variable",begin:b+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),l.C_BLOCK_COMMENT_MODE,l.C_LINE_COMMENT_MODE]},J=[l.APOS_STRING_MODE,l.QUOTE_STRING_MODE,D,L,T,I,{match:/\$\d+/},M];A.contains=J.concat({begin:/\{/,end:/\}/,keywords:h,contains:["self"].concat(J)});const ae=[].concat(F,A.contains),W=ae.concat([{begin:/\(/,end:/\)/,keywords:h,contains:["self"].concat(ae)}]),B={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:W},V={variants:[{match:[/class/,/\s+/,b,/\s+/,/extends/,/\s+/,c.concat(b,"(",c.concat(/\./,b),")*")],scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{match:[/class/,/\s+/,b],scope:{1:"keyword",3:"title.class"}}]},u={relevance:0,match:c.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:"title.class",keywords:{_:[...s,...d]}},f={label:"use_strict",className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},w={variants:[{match:[/function/,/\s+/,b,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:"keyword",3:"title.function"},label:"func.def",contains:[B],illegal:/%/},k={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"};function U(q){return c.concat("(?!",q.join("|"),")")}const Y={match:c.concat(/\b/,U([...o,"super","import"]),b,c.lookahead(/\(/)),className:"title.function",relevance:0},ne={begin:c.concat(/\./,c.lookahead(c.concat(b,/(?![0-9A-Za-z$_(])/))),end:b,excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},te={match:[/get|set/,/\s+/,b,/(?=\()/],className:{1:"keyword",3:"title.function"},contains:[{begin:/\(\)/},B]},z="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+l.UNDERSCORE_IDENT_RE+")\\s*=>",Z={match:[/const|var|let/,/\s+/,b,/\s*/,/=\s*/,/(async\s*)?/,c.lookahead(z)],keywords:"async",className:{1:"keyword",3:"title.function"},contains:[B]};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:h,exports:{PARAMS_CONTAINS:W,CLASS_REFERENCE:u},illegal:/#(?![$_A-z])/,contains:[l.SHEBANG({label:"shebang",binary:"node",relevance:5}),f,l.APOS_STRING_MODE,l.QUOTE_STRING_MODE,D,L,T,I,F,{match:/\$\d+/},M,u,{className:"attr",begin:b+c.lookahead(":"),relevance:0},Z,{begin:"("+l.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",relevance:0,contains:[F,l.REGEXP_MODE,{className:"function",begin:z,returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:l.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:h,contains:W}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:p.begin,end:p.end},{match:E},{begin:m.begin,"on:begin":m.isTrulyOpeningTag,end:m.end}],subLanguage:"xml",contains:[{begin:m.begin,end:m.end,skip:!0,contains:["self"]}]}]},w,{beginKeywords:"while if switch catch for"},{begin:"\\b(?!function)"+l.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,label:"func.def",contains:[B,l.inherit(l.TITLE_MODE,{begin:b,className:"title.function"})]},{match:/\.\.\./,relevance:0},ne,{match:"\\$"+b,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},contains:[B]},Y,k,V,te,{match:/\$[(.]/}]}}return He=a,He}var We,$n;function Ir(){if($n)return We;$n=1;function n(e){const t={className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},s={match:/[{}[\],:]/,className:"punctuation",relevance:0},d=["true","false","null"],o={scope:"literal",beginKeywords:d.join(" ")};return{name:"JSON",keywords:{literal:d},contains:[t,s,e.QUOTE_STRING_MODE,o,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE],illegal:"\\S"}}return We=n,We}var Ye,zn;function kr(){if(zn)return Ye;zn=1;var n="[0-9](_*[0-9])*",e=`\\.(${n})`,t="[0-9a-fA-F](_*[0-9a-fA-F])*",s={className:"number",variants:[{begin:`(\\b(${n})((${e})|\\.)?|(${e}))[eE][+-]?(${n})[fFdD]?\\b`},{begin:`\\b(${n})((${e})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{begin:`(${e})[fFdD]?\\b`},{begin:`\\b(${n})[fFdD]\\b`},{begin:`\\b0[xX]((${t})\\.?|(${t})?\\.(${t}))[pP][+-]?(${n})[fFdD]?\\b`},{begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${t})[lL]?\\b`},{begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}],relevance:0};function d(o){const i={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},r={className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a={className:"symbol",begin:o.UNDERSCORE_IDENT_RE+"@"},l={className:"subst",begin:/\$\{/,end:/\}/,contains:[o.C_NUMBER_MODE]},c={className:"variable",begin:"\\$"+o.UNDERSCORE_IDENT_RE},g={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[c,l]},{begin:"'",end:"'",illegal:/\n/,contains:[o.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[o.BACKSLASH_ESCAPE,c,l]}]};l.contains.push(g);const b={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+o.UNDERSCORE_IDENT_RE+")?"},p={className:"meta",begin:"@"+o.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[o.inherit(g,{className:"string"}),"self"]}]},E=s,m=o.COMMENT("/\\*","\\*/",{contains:[o.C_BLOCK_COMMENT_MODE]}),h={variants:[{className:"type",begin:o.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},N=h;return N.variants[1].contains=[h],h.variants[1].contains=[N],{name:"Kotlin",aliases:["kt","kts"],keywords:i,contains:[o.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),o.C_LINE_COMMENT_MODE,m,r,a,b,p,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:i,relevance:5,contains:[{begin:o.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[o.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[h,o.C_LINE_COMMENT_MODE,m],relevance:0},o.C_LINE_COMMENT_MODE,m,b,p,g,o.C_NUMBER_MODE]},m]},{begin:[/class|interface|trait/,/\s+/,o.UNDERSCORE_IDENT_RE],beginScope:{3:"title.class"},keywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},o.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,){\s]|$/,excludeBegin:!0,returnEnd:!0},b,p]},g,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:` +`},E]}}return Ye=d,Ye}var Ze,Kn;function xr(){if(Kn)return Ze;Kn=1;const n=a=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:a.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:a.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],s=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],d=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse(),i=s.concat(d);function r(a){const l=n(a),c=i,g="and or not only",b="[\\w-]+",p="("+b+"|@\\{"+b+"\\})",E=[],m=[],h=function(F){return{className:"string",begin:"~?"+F+".*?"+F}},N=function(F,J,ae){return{className:F,begin:J,relevance:ae}},O={$pattern:/[a-z-]+/,keyword:g,attribute:t.join(" ")},C={begin:"\\(",end:"\\)",contains:m,keywords:O,relevance:0};m.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,h("'"),h('"'),l.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},l.HEXCOLOR,C,N("variable","@@?"+b,10),N("variable","@\\{"+b+"\\}"),N("built_in","~?`[^`]*?`"),{className:"attribute",begin:b+"\\s*:",end:":",returnBegin:!0,excludeEnd:!0},l.IMPORTANT,{beginKeywords:"and not"},l.FUNCTION_DISPATCH);const M=m.concat({begin:/\{/,end:/\}/,contains:E}),A={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(m)},D={begin:p+"\\s*:",returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/},l.CSS_VARIABLE,{className:"attribute",begin:"\\b("+o.join("|")+")\\b",end:/(?=:)/,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:m}}]},L={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",keywords:O,returnEnd:!0,contains:m,relevance:0}},T={className:"variable",variants:[{begin:"@"+b+"\\s*:",relevance:15},{begin:"@"+b}],starts:{end:"[;}]",returnEnd:!0,contains:M}},I={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:p,end:/\{/}],returnBegin:!0,returnEnd:!0,illegal:`[<='$"]`,relevance:0,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,A,N("keyword","all\\b"),N("variable","@\\{"+b+"\\}"),{begin:"\\b("+e.join("|")+")\\b",className:"selector-tag"},l.CSS_NUMBER_MODE,N("selector-tag",p,0),N("selector-id","#"+p),N("selector-class","\\."+p,0),N("selector-tag","&",0),l.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",begin:":("+s.join("|")+")"},{className:"selector-pseudo",begin:":(:)?("+d.join("|")+")"},{begin:/\(/,end:/\)/,relevance:0,contains:M},{begin:"!important"},l.FUNCTION_DISPATCH]},H={begin:b+`:(:)?(${c.join("|")})`,returnBegin:!0,contains:[I]};return E.push(a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,L,T,H,D,I,A,l.FUNCTION_DISPATCH),{name:"Less",case_insensitive:!0,illegal:`[=>'/<($"]`,contains:E}}return Ze=r,Ze}var Xe,qn;function Dr(){if(qn)return Xe;qn=1;function n(e){const t="\\[=*\\[",s="\\]=*\\]",d={begin:t,end:s,contains:["self"]},o=[e.COMMENT("--(?!"+t+")","$"),e.COMMENT("--"+t,s,{contains:[d],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:o.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:o}].concat(o)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:t,end:s,contains:[d],relevance:5}])}}return Xe=n,Xe}var Ve,Gn;function Lr(){if(Gn)return Ve;Gn=1;function n(e){const t={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%\{/,end:/\}/},a={variants:[{begin:/\$\d/},{begin:t.concat(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])")},{begin:/[$%@][^\s\w{]/,relevance:0}]},l=[e.BACKSLASH_ESCAPE,i,a],c=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],g=(E,m,h="\\1")=>{const N=h==="\\1"?h:t.concat(h,m);return t.concat(t.concat("(?:",E,")"),m,/(?:\\.|[^\\\/])*?/,N,/(?:\\.|[^\\\/])*?/,h,d)},b=(E,m,h)=>t.concat(t.concat("(?:",E,")"),m,/(?:\\.|[^\\\/])*?/,h,d),p=[a,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{endsWithParent:!0}),r,{className:"string",contains:l,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{begin:g("s|tr|y",t.either(...c,{capture:!0}))},{begin:g("s|tr|y","\\(","\\)")},{begin:g("s|tr|y","\\[","\\]")},{begin:g("s|tr|y","\\{","\\}")}],relevance:2},{className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{begin:b("(?:m|qr)?",/\//,/\//)},{begin:b("m|qr",t.either(...c,{capture:!0}),/\1/)},{begin:b("m|qr",/\(/,/\)/)},{begin:b("m|qr",/\[/,/\]/)},{begin:b("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return i.contains=p,r.contains=p,{name:"Perl",aliases:["pl","pm"],keywords:o,contains:p}}return Qe=n,Qe}var Je,Wn;function Ur(){if(Wn)return Je;Wn=1;function n(e){const t={className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},s=/[a-zA-Z@][a-zA-Z0-9_]*/,a={"variable.language":["this","super"],$pattern:s,keyword:["while","export","sizeof","typedef","const","struct","for","union","volatile","static","mutable","if","do","return","goto","enum","else","break","extern","asm","case","default","register","explicit","typename","switch","continue","inline","readonly","assign","readwrite","self","@synchronized","id","typeof","nonatomic","IBOutlet","IBAction","strong","weak","copy","in","out","inout","bycopy","byref","oneway","__strong","__weak","__block","__autoreleasing","@private","@protected","@public","@try","@property","@end","@throw","@catch","@finally","@autoreleasepool","@synthesize","@dynamic","@selector","@optional","@required","@encode","@package","@import","@defs","@compatibility_alias","__bridge","__bridge_transfer","__bridge_retained","__bridge_retain","__covariant","__contravariant","__kindof","_Nonnull","_Nullable","_Null_unspecified","__FUNCTION__","__PRETTY_FUNCTION__","__attribute__","getter","setter","retain","unsafe_unretained","nonnull","nullable","null_unspecified","null_resettable","class","instancetype","NS_DESIGNATED_INITIALIZER","NS_UNAVAILABLE","NS_REQUIRES_SUPER","NS_RETURNS_INNER_POINTER","NS_INLINE","NS_AVAILABLE","NS_DEPRECATED","NS_ENUM","NS_OPTIONS","NS_SWIFT_UNAVAILABLE","NS_ASSUME_NONNULL_BEGIN","NS_ASSUME_NONNULL_END","NS_REFINED_FOR_SWIFT","NS_SWIFT_NAME","NS_SWIFT_NOTHROW","NS_DURING","NS_HANDLER","NS_ENDHANDLER","NS_VALUERETURN","NS_VOIDRETURN"],literal:["false","true","FALSE","TRUE","nil","YES","NO","NULL"],built_in:["dispatch_once_t","dispatch_queue_t","dispatch_sync","dispatch_async","dispatch_once"],type:["int","float","char","unsigned","signed","short","long","double","wchar_t","unichar","void","bool","BOOL","id|0","_Bool"]},l={$pattern:s,keyword:["@interface","@class","@protocol","@implementation"]};return{name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"],keywords:a,illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+l.keyword.join("|")+")\\b",end:/(\{|$)/,excludeEnd:!0,keywords:l,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}return Je=n,Je}var je,Yn;function Pr(){if(Yn)return je;Yn=1;function n(e){const t=e.regex,s=/(?![A-Za-z0-9])(?![$])/,d=t.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,s),o=t.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,s),i={scope:"variable",match:"\\$+"+d},r={scope:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{begin:/\?>/}]},a={scope:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]},l=e.inherit(e.APOS_STRING_MODE,{illegal:null}),c=e.inherit(e.QUOTE_STRING_MODE,{illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(a)}),g={begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/,contains:e.QUOTE_STRING_MODE.contains.concat(a),"on:begin":(W,B)=>{B.data._beginMatch=W[1]||W[2]},"on:end":(W,B)=>{B.data._beginMatch!==W[1]&&B.ignoreMatch()}},b=e.END_SAME_AS_BEGIN({begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/}),p=`[ +]`,E={scope:"string",variants:[c,l,g,b]},m={scope:"number",variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"}],relevance:0},h=["false","null","true"],N=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],O=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],M={keyword:N,literal:(W=>{const B=[];return W.forEach(V=>{B.push(V),V.toLowerCase()===V?B.push(V.toUpperCase()):B.push(V.toLowerCase())}),B})(h),built_in:O},A=W=>W.map(B=>B.replace(/\|\d+$/,"")),D={variants:[{match:[/new/,t.concat(p,"+"),t.concat("(?!",A(O).join("\\b|"),"\\b)"),o],scope:{1:"keyword",4:"title.class"}}]},L=t.concat(d,"\\b(?!\\()"),T={variants:[{match:[t.concat(/::/,t.lookahead(/(?!class\b)/)),L],scope:{2:"variable.constant"}},{match:[/::/,/class/],scope:{2:"variable.language"}},{match:[o,t.concat(/::/,t.lookahead(/(?!class\b)/)),L],scope:{1:"title.class",3:"variable.constant"}},{match:[o,t.concat("::",t.lookahead(/(?!class\b)/))],scope:{1:"title.class"}},{match:[o,/::/,/class/],scope:{1:"title.class",3:"variable.language"}}]},I={scope:"attr",match:t.concat(d,t.lookahead(":"),t.lookahead(/(?!::)/))},H={relevance:0,begin:/\(/,end:/\)/,keywords:M,contains:[I,i,T,e.C_BLOCK_COMMENT_MODE,E,m,D]},F={relevance:0,match:[/\b/,t.concat("(?!fn\\b|function\\b|",A(N).join("\\b|"),"|",A(O).join("\\b|"),"\\b)"),d,t.concat(p,"*"),t.lookahead(/(?=\()/)],scope:{3:"title.function.invoke"},contains:[H]};H.contains.push(F);const J=[I,T,e.C_BLOCK_COMMENT_MODE,E,m,D],ae={begin:t.concat(/#\[\s*/,o),beginScope:"meta",end:/]/,endScope:"meta",keywords:{literal:h,keyword:["new","array"]},contains:[{begin:/\[/,end:/]/,keywords:{literal:h,keyword:["new","array"]},contains:["self",...J]},...J,{scope:"meta",match:o}]};return{case_insensitive:!1,keywords:M,contains:[ae,e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},r,{scope:"variable.language",match:/\$this\b/},i,F,T,{match:[/const/,/\s/,d],scope:{1:"keyword",3:"variable.constant"}},D,{scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"},e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:M,contains:["self",i,T,e.C_BLOCK_COMMENT_MODE,E,m]}]},{scope:"class",variants:[{beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait",illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/,contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{beginKeywords:"use",relevance:0,end:";",contains:[{match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},E,m]}}return je=n,je}var en,Zn;function Fr(){if(Zn)return en;Zn=1;function n(e){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}return en=n,en}var nn,Xn;function $r(){if(Xn)return nn;Xn=1;function n(e){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}return nn=n,nn}var tn,Vn;function zr(){if(Vn)return tn;Vn=1;function n(e){const t=e.regex,s=new RegExp("[\\p{XID_Start}_]\\p{XID_Continue}*","u"),d=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],a={$pattern:/[A-Za-z]\w+|__\w+__/,keyword:d,built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"],literal:["__debug__","Ellipsis","False","None","NotImplemented","True"],type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"]},l={className:"meta",begin:/^(>>>|\.\.\.) /},c={className:"subst",begin:/\{/,end:/\}/,keywords:a,illegal:/#/},g={begin:/\{\{/,relevance:0},b={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,l],relevance:10},{begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,l],relevance:10},{begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,l,g,c]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,l,g,c]},{begin:/([uU]|[rR])'/,end:/'/,relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/,end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,g,c]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,g,c]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},p="[0-9](_?[0-9])*",E=`(\\b(${p}))?\\.(${p})|\\b(${p})\\.`,m=`\\b|${d.join("|")}`,h={className:"number",relevance:0,variants:[{begin:`(\\b(${p})|(${E}))[eE][+-]?(${p})[jJ]?(?=${m})`},{begin:`(${E})[jJ]?`},{begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${m})`},{begin:`\\b0[bB](_?[01])+[lL]?(?=${m})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${m})`},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${m})`},{begin:`\\b(${p})[jJ](?=${m})`}]},N={className:"comment",begin:t.lookahead(/# type:/),end:/$/,keywords:a,contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},O={className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:a,contains:["self",l,h,b,e.HASH_COMMENT_MODE]}]};return c.contains=[b,h,l],{name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:a,illegal:/(<\/|\?)|=>/,contains:[l,h,{begin:/\bself\b/},{beginKeywords:"if",relevance:0},b,N,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,s],scope:{1:"keyword",3:"title.function"},contains:[O]},{variants:[{match:[/\bclass/,/\s+/,s,/\s*/,/\(\s*/,s,/\s*\)/]},{match:[/\bclass/,/\s+/,s]}],scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[h,O,b]}]}}return tn=n,tn}var rn,Qn;function Kr(){if(Qn)return rn;Qn=1;function n(e){return{aliases:["pycon"],contains:[{className:"meta.prompt",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}return rn=n,rn}var an,Jn;function qr(){if(Jn)return an;Jn=1;function n(e){const t=e.regex,s=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,d=t.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),o=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,i=t.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/);return{name:"R",keywords:{$pattern:s,keyword:"function if in break next repeat else for while",literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10",built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm"},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/,starts:{end:t.lookahead(t.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)),endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{scope:"variable",variants:[{match:s},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0}]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}]}),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE],variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"',relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{1:"operator",2:"number"},match:[o,d]},{scope:{1:"operator",2:"number"},match:[/%[^%]*%/,d]},{scope:{1:"punctuation",2:"number"},match:[i,d]},{scope:{2:"number"},match:[/[^a-zA-Z0-9._]|^/,d]}]},{scope:{3:"operator"},match:[s,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:o},{match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:i},{begin:"`",end:"`",contains:[{begin:/\\./}]}]}}return an=n,an}var sn,jn;function Gr(){if(jn)return sn;jn=1;function n(e){const t=e.regex,s={className:"title.function.invoke",relevance:0,begin:t.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,t.lookahead(/\s*\(/))},d="([ui](8|16|32|64|128|size)|f(32|64))?",o=["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"],i=["true","false","Some","None","Ok","Err"],r=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],a=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"];return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:a,keyword:o,literal:i,built_in:r},illegal:""},s]}}return sn=n,sn}var on,et;function Hr(){if(et)return on;et=1;const n=r=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:r.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:r.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),e=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],t=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],s=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],d=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],o=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse();function i(r){const a=n(r),l=d,c=s,g="@[a-z-]+",b="and or not only",E={className:"variable",begin:"(\\$"+"[a-zA-Z-][a-zA-Z0-9_-]*"+")\\b",relevance:0};return{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,a.CSS_NUMBER_MODE,{className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},a.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag",begin:"\\b("+e.join("|")+")\\b",relevance:0},{className:"selector-pseudo",begin:":("+c.join("|")+")"},{className:"selector-pseudo",begin:":(:)?("+l.join("|")+")"},E,{begin:/\(/,end:/\)/,contains:[a.CSS_NUMBER_MODE]},a.CSS_VARIABLE,{className:"attribute",begin:"\\b("+o.join("|")+")\\b"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:/:/,end:/[;}{]/,relevance:0,contains:[a.BLOCK_COMMENT,E,a.HEXCOLOR,a.CSS_NUMBER_MODE,r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,a.IMPORTANT,a.FUNCTION_DISPATCH]},{begin:"@(page|font-face)",keywords:{$pattern:g,keyword:"@page @font-face"}},{begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/,keyword:b,attribute:t.join(" ")},contains:[{begin:g,className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute"},E,r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,a.HEXCOLOR,a.CSS_NUMBER_MODE]},a.FUNCTION_DISPATCH]}}return on=i,on}var cn,nt;function Wr(){if(nt)return cn;nt=1;function n(e){return{name:"Shell Session",aliases:["console","shellsession"],contains:[{className:"meta.prompt",begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/,subLanguage:"bash"}}]}}return cn=n,cn}var ln,tt;function Yr(){if(tt)return ln;tt=1;function n(e){const t=e.regex,s=e.COMMENT("--","$"),d={className:"string",variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},o={begin:/"/,end:/"/,contains:[{begin:/""/}]},i=["true","false","unknown"],r=["double precision","large object","with timezone","without timezone"],a=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],l=["add","asc","collation","desc","final","first","last","view"],c=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year"],g=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],b=["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"],p=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],E=g,m=[...c,...l].filter(M=>!g.includes(M)),h={className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},N={className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,relevance:0},O={begin:t.concat(/\b/,t.either(...E),/\s*\(/),relevance:0,keywords:{built_in:E}};function C(M,{exceptions:A,when:D}={}){const L=D;return A=A||[],M.map(T=>T.match(/\|\d+$/)||A.includes(T)?T:L(T)?`${T}|0`:T)}return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{$pattern:/\b[\w\.]+/,keyword:C(m,{when:M=>M.length<3}),literal:i,type:a,built_in:b},contains:[{begin:t.either(...p),relevance:0,keywords:{$pattern:/[\w\.]+/,keyword:m.concat(p),literal:i,type:a}},{className:"type",begin:t.either(...r)},O,h,d,o,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,s,N]}}return ln=n,ln}var dn,rt;function Zr(){if(rt)return dn;rt=1;function n(T){return T?typeof T=="string"?T:T.source:null}function e(T){return t("(?=",T,")")}function t(...T){return T.map(H=>n(H)).join("")}function s(T){const I=T[T.length-1];return typeof I=="object"&&I.constructor===Object?(T.splice(T.length-1,1),I):{}}function d(...T){return"("+(s(T).capture?"":"?:")+T.map(F=>n(F)).join("|")+")"}const o=T=>t(/\b/,T,/\w$/.test(T)?/\b/:/\B/),i=["Protocol","Type"].map(o),r=["init","self"].map(o),a=["Any","Self"],l=["actor","any","associatedtype","async","await",/as\?/,/as!/,"as","borrowing","break","case","catch","class","consume","consuming","continue","convenience","copy","default","defer","deinit","didSet","distributed","do","dynamic","each","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","isolated","nonisolated","lazy","let","macro","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],c=["false","nil","true"],g=["assignment","associativity","higherThan","left","lowerThan","none","right"],b=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warning"],p=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],E=d(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),m=d(E,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),h=t(E,m,"*"),N=d(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),O=d(N,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),C=t(N,O,"*"),M=t(/[A-Z]/,O,"*"),A=["attached","autoclosure",t(/convention\(/,d("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","freestanding","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",t(/objc\(/,C,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","resultBuilder","Sendable","testable","UIApplicationMain","unchecked","unknown","usableFromInline","warn_unqualified_access"],D=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"];function L(T){const I={match:/\s+/,relevance:0},H=T.COMMENT("/\\*","\\*/",{contains:["self"]}),F=[T.C_LINE_COMMENT_MODE,H],J={match:[/\./,d(...i,...r)],className:{2:"keyword"}},ae={match:t(/\./,d(...l)),relevance:0},W=l.filter($=>typeof $=="string").concat(["_|0"]),B=l.filter($=>typeof $!="string").concat(a).map(o),V={variants:[{className:"keyword",match:d(...B,...r)}]},u={$pattern:d(/\b\w+/,/#\w+/),keyword:W.concat(b),literal:c},f=[J,ae,V],w={match:t(/\./,d(...p)),relevance:0},k={className:"built_in",match:t(/\b/,d(...p),/(?=\()/)},U=[w,k],Y={match:/->/,relevance:0},ne={className:"operator",relevance:0,variants:[{match:h},{match:`\\.(\\.|${m})+`}]},te=[Y,ne],z="([0-9]_*)+",Z="([0-9a-fA-F]_*)+",q={className:"number",relevance:0,variants:[{match:`\\b(${z})(\\.(${z}))?([eE][+-]?(${z}))?\\b`},{match:`\\b0x(${Z})(\\.(${Z}))?([pP][+-]?(${z}))?\\b`},{match:/\b0o([0-7]_*)+\b/},{match:/\b0b([01]_*)+\b/}]},K=($="")=>({className:"subst",variants:[{match:t(/\\/,$,/[0\\tnr"']/)},{match:t(/\\/,$,/u\{[0-9a-fA-F]{1,8}\}/)}]}),Q=($="")=>({className:"subst",match:t(/\\/,$,/[\t ]*(?:[\r\n]|\r\n)/)}),j=($="")=>({className:"subst",label:"interpol",begin:t(/\\/,$,/\(/),end:/\)/}),ee=($="")=>({begin:t($,/"""/),end:t(/"""/,$),contains:[K($),Q($),j($)]}),ie=($="")=>({begin:t($,/"/),end:t(/"/,$),contains:[K($),j($)]}),oe={className:"string",variants:[ee(),ee("#"),ee("##"),ee("###"),ie(),ie("#"),ie("##"),ie("###")]},ge=[T.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[T.BACKSLASH_ESCAPE]}],Te={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//,contains:ge},re=$=>{const Oe=t($,/\//),Ne=t(/\//,$);return{begin:Oe,end:Ne,contains:[...ge,{scope:"comment",begin:`#(?!.*${Ne})`,end:/$/}]}},Se={scope:"regexp",variants:[re("###"),re("##"),re("#"),Te]},_e={match:t(/`/,C,/`/)},S={className:"variable",match:/\$\d+/},ve={className:"variable",match:`\\$${O}+`},G=[_e,S,ve],P={match:/(@|#(un)?)available/,scope:"keyword",starts:{contains:[{begin:/\(/,end:/\)/,keywords:D,contains:[...te,q,oe]}]}},Ee={scope:"keyword",match:t(/@/,d(...A))},le={scope:"meta",match:t(/@/,C)},de=[P,Ee,le],ce={match:e(/\b[A-Z]/),relevance:0,contains:[{className:"type",match:t(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,O,"+")},{className:"type",match:M,relevance:0},{match:/[?!]+/,relevance:0},{match:/\.\.\./,relevance:0},{match:t(/\s+&\s+/,e(M)),relevance:0}]},_={begin://,keywords:u,contains:[...F,...f,...de,Y,ce]};ce.contains.push(_);const y={match:t(C,/\s*:/),keywords:"_|0",relevance:0},v={begin:/\(/,end:/\)/,relevance:0,keywords:u,contains:["self",y,...F,Se,...f,...U,...te,q,oe,...G,...de,ce]},x={begin://,keywords:"repeat each",contains:[...F,ce]},X={begin:d(e(t(C,/\s*:/)),e(t(C,/\s+/,C,/\s*:/))),end:/:/,relevance:0,contains:[{className:"keyword",match:/\b_\b/},{className:"params",match:C}]},se={begin:/\(/,end:/\)/,keywords:u,contains:[X,...F,...f,...te,q,oe,...de,ce,v],endsParent:!0,illegal:/["']/},Me={match:[/(func|macro)/,/\s+/,d(_e.match,C,h)],className:{1:"keyword",3:"title.function"},contains:[x,se,I],illegal:[/\[/,/%/]},Ce={match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:"keyword"},contains:[x,se,I],illegal:/\[|%/},ht={match:[/operator/,/\s+/,h],className:{1:"keyword",3:"title"}},yt={begin:[/precedencegroup/,/\s+/,M],className:{1:"keyword",3:"title"},contains:[ce],keywords:[...g,...c],end:/}/};for(const $ of oe.variants){const Oe=$.contains.find(Tt=>Tt.label==="interpol");Oe.keywords=u;const Ne=[...f,...U,...te,q,oe,...G];Oe.contains=[...Ne,{begin:/\(/,end:/\)/,contains:["self",...Ne]}]}return{name:"Swift",keywords:u,contains:[...F,Me,Ce,{beginKeywords:"struct protocol class extension enum actor",end:"\\{",excludeEnd:!0,keywords:u,contains:[T.inherit(T.TITLE_MODE,{className:"title.class",begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...f]},ht,yt,{beginKeywords:"import",end:/$/,contains:[...F],relevance:0},Se,...f,...U,...te,q,oe,...G,...de,ce,v]}}return dn=L,dn}var un,at;function Xr(){if(at)return un;at=1;function n(e){const t="true false yes no null",s="[\\w#;/?:@&=+$,.~*'()[\\]]+",d={className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ ]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ ]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ ]|$)"}]},o={className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]},i={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,o]},r=e.inherit(i,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),b={className:"number",begin:"\\b"+"[0-9]{4}(-[0-9][0-9]){0,2}"+"([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?"+"(\\.[0-9]*)?"+"([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?"+"\\b"},p={end:",",endsWithParent:!0,excludeEnd:!0,keywords:t,relevance:0},E={begin:/\{/,end:/\}/,contains:[p],illegal:"\\n",relevance:0},m={begin:"\\[",end:"\\]",contains:[p],illegal:"\\n",relevance:0},h=[d,{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+s},{className:"type",begin:"!<"+s+">"},{className:"type",begin:"!"+s},{className:"type",begin:"!!"+s},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:t,keywords:{literal:t}},b,{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},E,m,i],N=[...h];return N.pop(),N.push(r),p.contains=N,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:h}}return un=n,un}var gn,it;function Vr(){if(it)return gn;it=1;const n="[A-Za-z$_][0-9A-Za-z$_]*",e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],t=["true","false","null","undefined","NaN","Infinity"],s=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],d=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],o=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],i=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],r=[].concat(o,s,d);function a(c){const g=c.regex,b=(K,{after:Q})=>{const j="",end:""},m=/<[A-Za-z0-9\\._:-]+\s*\/>/,h={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(K,Q)=>{const j=K[0].length+K.index,ee=K.input[j];if(ee==="<"||ee===","){Q.ignoreMatch();return}ee===">"&&(b(K,{after:j})||Q.ignoreMatch());let ie;const oe=K.input.substring(j);if(ie=oe.match(/^\s*=/)){Q.ignoreMatch();return}if((ie=oe.match(/^\s+extends\s+/))&&ie.index===0){Q.ignoreMatch();return}}},N={$pattern:n,keyword:e,literal:t,built_in:r,"variable.language":i},O="[0-9](_?[0-9])*",C=`\\.(${O})`,M="0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*",A={className:"number",variants:[{begin:`(\\b(${M})((${C})|\\.)?|(${C}))[eE][+-]?(${O})\\b`},{begin:`\\b(${M})\\b((${C})\\b|\\.)?|(${C})\\b`},{begin:"\\b(0|[1-9](_?[0-9])*)n\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*n?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*n?\\b"},{begin:"\\b0[0-7]+n?\\b"}],relevance:0},D={className:"subst",begin:"\\$\\{",end:"\\}",keywords:N,contains:[]},L={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,D],subLanguage:"xml"}},T={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,D],subLanguage:"css"}},I={begin:"gql`",end:"",starts:{end:"`",returnEnd:!1,contains:[c.BACKSLASH_ESCAPE,D],subLanguage:"graphql"}},H={className:"string",begin:"`",end:"`",contains:[c.BACKSLASH_ESCAPE,D]},J={className:"comment",variants:[c.COMMENT(/\/\*\*(?!\/)/,"\\*/",{relevance:0,contains:[{begin:"(?=@[A-Za-z]+)",relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"},{className:"type",begin:"\\{",end:"\\}",excludeEnd:!0,excludeBegin:!0,relevance:0},{className:"variable",begin:p+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),c.C_BLOCK_COMMENT_MODE,c.C_LINE_COMMENT_MODE]},ae=[c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,L,T,I,H,{match:/\$\d+/},A];D.contains=ae.concat({begin:/\{/,end:/\}/,keywords:N,contains:["self"].concat(ae)});const W=[].concat(J,D.contains),B=W.concat([{begin:/\(/,end:/\)/,keywords:N,contains:["self"].concat(W)}]),V={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:N,contains:B},u={variants:[{match:[/class/,/\s+/,p,/\s+/,/extends/,/\s+/,g.concat(p,"(",g.concat(/\./,p),")*")],scope:{1:"keyword",3:"title.class",5:"keyword",7:"title.class.inherited"}},{match:[/class/,/\s+/,p],scope:{1:"keyword",3:"title.class"}}]},f={relevance:0,match:g.either(/\bJSON/,/\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,/\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,/\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/),className:"title.class",keywords:{_:[...s,...d]}},w={label:"use_strict",className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},k={variants:[{match:[/function/,/\s+/,p,/(?=\s*\()/]},{match:[/function/,/\s*(?=\()/]}],className:{1:"keyword",3:"title.function"},label:"func.def",contains:[V],illegal:/%/},U={relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"};function Y(K){return g.concat("(?!",K.join("|"),")")}const ne={match:g.concat(/\b/,Y([...o,"super","import"]),p,g.lookahead(/\(/)),className:"title.function",relevance:0},te={begin:g.concat(/\./,g.lookahead(g.concat(p,/(?![0-9A-Za-z$_(])/))),end:p,excludeBegin:!0,keywords:"prototype",className:"property",relevance:0},z={match:[/get|set/,/\s+/,p,/(?=\()/],className:{1:"keyword",3:"title.function"},contains:[{begin:/\(\)/},V]},Z="(\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)|"+c.UNDERSCORE_IDENT_RE+")\\s*=>",q={match:[/const|var|let/,/\s+/,p,/\s*/,/=\s*/,/(async\s*)?/,g.lookahead(Z)],keywords:"async",className:{1:"keyword",3:"title.function"},contains:[V]};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:N,exports:{PARAMS_CONTAINS:B,CLASS_REFERENCE:f},illegal:/#(?![$_A-z])/,contains:[c.SHEBANG({label:"shebang",binary:"node",relevance:5}),w,c.APOS_STRING_MODE,c.QUOTE_STRING_MODE,L,T,I,H,J,{match:/\$\d+/},A,f,{className:"attr",begin:p+g.lookahead(":"),relevance:0},q,{begin:"("+c.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",relevance:0,contains:[J,c.REGEXP_MODE,{className:"function",begin:Z,returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:c.UNDERSCORE_IDENT_RE,relevance:0},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:N,contains:B}]}]},{begin:/,/,relevance:0},{match:/\s+/,relevance:0},{variants:[{begin:E.begin,end:E.end},{match:m},{begin:h.begin,"on:begin":h.isTrulyOpeningTag,end:h.end}],subLanguage:"xml",contains:[{begin:h.begin,end:h.end,skip:!0,contains:["self"]}]}]},k,{beginKeywords:"while if switch catch for"},{begin:"\\b(?!function)"+c.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{",returnBegin:!0,label:"func.def",contains:[V,c.inherit(c.TITLE_MODE,{begin:p,className:"title.function"})]},{match:/\.\.\./,relevance:0},te,{match:"\\$"+p,relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"},contains:[V]},ne,U,u,z,{match:/\$[(.]/}]}}function l(c){const g=a(c),b=n,p=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],E={beginKeywords:"namespace",end:/\{/,excludeEnd:!0,contains:[g.exports.CLASS_REFERENCE]},m={beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:{keyword:"interface extends",built_in:p},contains:[g.exports.CLASS_REFERENCE]},h={className:"meta",relevance:10,begin:/^\s*['"]use strict['"]/},N=["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"],O={$pattern:n,keyword:e.concat(N),literal:t,built_in:r.concat(p),"variable.language":i},C={className:"meta",begin:"@"+b},M=(D,L,T)=>{const I=D.contains.findIndex(H=>H.label===L);if(I===-1)throw new Error("can not find mode to replace");D.contains.splice(I,1,T)};Object.assign(g.keywords,O),g.exports.PARAMS_CONTAINS.push(C),g.contains=g.contains.concat([C,E,m]),M(g,"shebang",c.SHEBANG()),M(g,"use_strict",h);const A=g.contains.find(D=>D.label==="func.def");return A.relevance=0,Object.assign(g,{name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),g}return gn=l,gn}var bn,st;function Qr(){if(st)return bn;st=1;function n(e){const t=e.regex,s={className:"string",begin:/"(""|[^/n])"C\b/},d={className:"string",begin:/"/,end:/"/,illegal:/\n/,contains:[{begin:/""/}]},o=/\d{1,2}\/\d{1,2}\/\d{4}/,i=/\d{4}-\d{1,2}-\d{1,2}/,r=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,a=/\d{1,2}(:\d{1,2}){1,2}/,l={className:"literal",variants:[{begin:t.concat(/# */,t.either(i,o),/ *#/)},{begin:t.concat(/# */,a,/ *#/)},{begin:t.concat(/# */,r,/ *#/)},{begin:t.concat(/# */,t.either(i,o),/ +/,t.either(r,a),/ *#/)}]},c={className:"number",relevance:0,variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},g={className:"label",begin:/^\w+:/},b=e.COMMENT(/'''/,/$/,{contains:[{className:"doctag",begin:/<\/?/,end:/>/}]}),p=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]});return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0,classNameAliases:{label:"symbol"},keywords:{keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield",built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort",type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort",literal:"true false nothing"},illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[s,d,l,c,g,b,p,{className:"meta",begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/,end:/$/,keywords:{keyword:"const disable else elseif enable end externalsource if region then"},contains:[p]}]}}return bn=n,bn}var pn,ot;function Jr(){if(ot)return pn;ot=1;function n(e){e.regex;const t=e.COMMENT(/\(;/,/;\)/);t.contains.push("self");const s=e.COMMENT(/;;/,/$/),d=["anyfunc","block","br","br_if","br_table","call","call_indirect","data","drop","elem","else","end","export","func","global.get","global.set","local.get","local.set","local.tee","get_global","get_local","global","if","import","local","loop","memory","memory.grow","memory.size","module","mut","nop","offset","param","result","return","select","set_global","set_local","start","table","tee_local","then","type","unreachable"],o={begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:"keyword",3:"title.function"}},i={className:"variable",begin:/\$[\w_]+/},r={match:/(\((?!;)|\))+/,className:"punctuation",relevance:0},a={className:"number",relevance:0,match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/},l={match:/(i32|i64|f32|f64)(?!\.)/,className:"type"},c={className:"keyword",match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/};return{name:"WebAssembly",keywords:{$pattern:/[\w.]+/,keyword:d},contains:[s,t,{match:[/(?:offset|align)/,/\s*/,/=/],className:{1:"keyword",3:"operator"}},i,r,o,e.QUOTE_STRING_MODE,l,c,a]}}return pn=n,pn}var R=Er;R.registerLanguage("xml",mr());R.registerLanguage("bash",fr());R.registerLanguage("c",Nr());R.registerLanguage("cpp",hr());R.registerLanguage("csharp",yr());R.registerLanguage("css",Tr());R.registerLanguage("markdown",Sr());R.registerLanguage("diff",vr());R.registerLanguage("ruby",Or());R.registerLanguage("go",wr());R.registerLanguage("graphql",Rr());R.registerLanguage("ini",Ar());R.registerLanguage("java",Mr());R.registerLanguage("javascript",Cr());R.registerLanguage("json",Ir());R.registerLanguage("kotlin",kr());R.registerLanguage("less",xr());R.registerLanguage("lua",Dr());R.registerLanguage("makefile",Lr());R.registerLanguage("perl",Br());R.registerLanguage("objectivec",Ur());R.registerLanguage("php",Pr());R.registerLanguage("php-template",Fr());R.registerLanguage("plaintext",$r());R.registerLanguage("python",zr());R.registerLanguage("python-repl",Kr());R.registerLanguage("r",qr());R.registerLanguage("rust",Gr());R.registerLanguage("scss",Hr());R.registerLanguage("shell",Wr());R.registerLanguage("sql",Yr());R.registerLanguage("swift",Zr());R.registerLanguage("yaml",Xr());R.registerLanguage("typescript",Vr());R.registerLanguage("vbnet",Qr());R.registerLanguage("wasm",Jr());R.HighlightJS=R;R.default=R;var jr=R;const na=St(jr);export{na as H}; diff --git a/products/phone/src/main/resources/resfile/dist/common2.js b/products/phone/src/main/resources/resfile/dist/common2.js new file mode 100644 index 0000000000000000000000000000000000000000..b285ce54315ce5ef89349a4cf37c92c0edfa6fd5 --- /dev/null +++ b/products/phone/src/main/resources/resfile/dist/common2.js @@ -0,0 +1 @@ +var o=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function l(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}export{o as c,l as g}; 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..22f26631b3e4abd8cb4e5d95929a8297063e2504 --- /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..8b127d61c6d187f892486dfefc766ab9d94e4307 --- /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": "HMOS World" + }, + { + "name": "homePage_title", + "value": "HMOS World" + }, + { + "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..9a3d181226ca9b0b5e41e26e769d9e1cccc724a9 --- /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