diff --git a/README.md b/README.md index 8d95c288..5241eec4 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ -# Harmonyos Tutorial.《跟老卫学HarmonyOS开发》 +# HarmonyOS Tutorial. 《跟老卫学HarmonyOS开发》《鸿蒙HarmonyOS手机应用开发实战》《鸿蒙HarmonyOS应用开发从入门到精通》《鸿蒙HarmonyOS应用开发入门》《鸿蒙之光HarmonyOS NEXT原生应用开发入门》《鸿蒙之光HarmonyOS 6应用开发入门》《鸿蒙架构师修炼之道》源码 -![](images/logo.jpg) +![](images/harmonyOS_logo.png) -*Harmonyos Tutorial*, is a book about how to develop Harmonyos applications. +*HarmonyOS Tutorial*, is a book about how to develop HarmonyOS applications. -《跟老卫学HarmonyOS开发》是一本 HarmonyOS 应用开发的开源学习教程,主要介绍如何从0开始开发 HarmonyOS 应用。本书包括最新版本 HarmonyOS 2.0 中的新特性。图文并茂,并通过大量实例带你走近 HarmonyOS 的世界! +《跟老卫学HarmonyOS开发》是一本 HarmonyOS 应用开发的开源学习教程,主要介绍如何从0开始开发 HarmonyOS 应用。本书包括最新版本HarmonyOS(HarmonyOS 6)中的新特性。图文并茂,并通过大量实例带你走进 HarmonyOS 的世界! -本书业余时间所著,水平有限、时间紧张,难免疏漏,欢迎指正, +本书业余时间所著,书中如有错漏之处,敬请斧正,欢迎读者与笔者联系。 -## Summary 目录 +对于仓颉应用开发,请参阅《[跟老卫学仓颉编程语言开发](https://github.com/waylau/cangjie-programming-language-tutorial)》。 + +## Summary 目录(按发表时间升序排序) + +* [什么是鸿蒙、OpenHarmony、HarmonyOS](https://waylau.com/what-is-harmonyos/) * [HarmonyOS初探01——下载安装DevEco Studio](https://developer.huawei.com/consumer/cn/forum/topic/0201427672244370691?fid=0101303901040230869) * [HarmonyOS初探02——开发第一个HarmonyOS应用](https://developer.huawei.com/consumer/cn/forum/topic/0201427689906950692?fid=0101303901040230869) * [HarmonyOS初探03——DevEco Studio创建应用问题ERROR Unable to tunnel through proxy. Proxy returns HTTP1.1 403](https://developer.huawei.com/consumer/cn/forum/topic/0201428884516950010?fid=0101303901040230869) @@ -20,13 +24,194 @@ * [HarmonyOS初探06——使用DevEco Studio模拟器端口被占用无法启动](https://developer.huawei.com/consumer/cn/forum/topic/0204428887502690016?fid=0101303901040230869) * [HarmonyOS初探07——使用DevEco Studio预览器](https://developer.huawei.com/consumer/cn/forum/topic/0201442998449480482?fid=0101303901040230869) * [DevEco Studio 2.0.12.201使用报错“This device type does not match the module profile.”](https://developer.huawei.com/consumer/cn/forum/topic/0201435470610010153?fid=26) +* [DevEco Studio 启用Java预览器](https://developer.huawei.com/consumer/cn/forum/topic/0201663534596920806?fid=0101591351254000314) * [HarmonyOS之Ability01——AbilitySlice间导航](https://developer.huawei.com/consumer/cn/forum/topic/0202443001580950502?fid=0101303901040230869) * [HarmonyOS之线程01——ParallelTaskDispatcher派发任务](https://developer.huawei.com/consumer/cn/forum/topic/0204460939630370009?fid=0101303901040230869) * [HarmonyOS之线程02——EventHandler处理线程间通信](https://developer.huawei.com/consumer/cn/forum/topic/0204461048552100015?fid=0101303901040230869) -* [获取图像属性失败!java.lang.IllegalStateException: image data source invalid 热门度2](https://developer.huawei.com/consumer/cn/forum/topic/0201481849198120096?fid=0101303901040230869&pid=0301492195334980932) +* [获取图像属性失败!java.lang.IllegalStateException: image data source invalid](https://developer.huawei.com/consumer/cn/forum/topic/0201481849198120096?fid=0101303901040230869&pid=0301492195334980932) +* [只要5分钟开发一个HarmonyOS鸿蒙应用——HelloWorld](https://www.bilibili.com/video/BV1Qh411U7Go?share_source=copy_web)(视频) +* [HarmonyOS Page与AbilitySlice生命周期](https://developer.huawei.com/consumer/cn/forum/topic/0202573163094170174?fid=0101303901040230869) +* [理解HarmonyOS Service Ability](https://developer.huawei.com/consumer/cn/forum/topic/0202578737701770050?fid=0101303901040230869) +* [一个HarmonyOS Service Ability生命周期的例子](https://developer.huawei.com/consumer/cn/forum/topic/0201578738626200065?fid=0101303901040230869) +* [理解HarmonyOS Data Ability](https://developer.huawei.com/consumer/cn/forum/topic/0204578739122210072?fid=0101303901040230869) +* [HarmonyOS DataAbilityHelper访问文件的例子](https://developer.huawei.com/consumer/cn/forum/topic/0202578740019440052?fid=0101303901040230869) +* [HarmonyOS DataAbilityHelper访问数据库的例子](https://developer.huawei.com/consumer/cn/forum/topic/0202578740728040053?fid=0101303901040230869) +* [理解HarmonyOS Intent ](https://developer.huawei.com/consumer/cn/forum/topic/0202578742549140054?fid=0101303901040230869) +* [使用Java轻松实现一个HarmonyOS服务卡片](https://developer.huawei.com/consumer/cn/forum/topic/0201592758640690342?fid=0101303901040230869) +* [HarmonyOS实现跨设备迁移与回迁](https://developer.huawei.com/consumer/cn/forum/topic/0201628139884080274?fid=0101303901040230869) +* [HarmonyOS实现多设备协同](https://developer.huawei.com/consumer/cn/forum/topic/0201628168180890276?fid=0101303901040230869) +* [HarmonyOS跳转到系统应用拨号盘](https://developer.huawei.com/consumer/cn/forum/topic/0201659093603710707?fid=0101303901040230869) +* [HarmonyOS跳转到应用管理](https://developer.huawei.com/consumer/cn/forum/topic/0202660722259570963?fid=0101591351254000314) +* [HarmonyOS跳转到搜索](https://developer.huawei.com/consumer/cn/forum/topic/0202663401260600008?fid=0101591351254000314) +* [HarmonyOS跳转到指定URI进行访问](https://developer.huawei.com/consumer/cn/forum/topic/0201673625843820338?fid=0101591351254000314) +* [HarmonyOS的TextField的text_alignment属性值已经变更](https://developer.huawei.com/consumer/cn/blog/topic/03664410174460106) +* [HarmonyOS的vp、fp与Android的dp、sp联系与区别](https://developer.huawei.com/consumer/cn/forum/topic/0202669455953940178?fid=0101303901040230869) +* [HarmonyOS JS UI之Chart、 Switch组件的组合使用](https://developer.huawei.com/consumer/cn/forum/topic/0202678203858060455?fid=0101591351254000314) +* [HarmonyOS的Service、原子化服务、服务卡片的区别](https://developer.huawei.com/consumer/cn/forum/topic/0201681241634630549?fid=0101610563345550409) +* [HarmonyOS 多entry下应用启动报错“The type of the target device does not match the deviceType configured in the config.json”的解决]( +https://developer.huawei.com/consumer/cn/forum/topic/0202692632918480755?fid=0101610563345550409) +* [HarmonyOS获取系统内存大小、可用内存](https://developer.huawei.com/consumer/cn/forum/topic/0202700471997530007?fid=0101591351254000314) +* [#HarmonyOS挑战赛第二期#仿抖音短视频应用]( +https://developer.huawei.com/consumer/cn/forum/topic/0201692989697260758?fid=0101303901040230869) +* [#HarmonyOS挑战赛第三期#“心目中的1024程序员节”爱“HarmonyOS”](https://developer.huawei.com/consumer/cn/forum/topic/0201697127022460928?fid=0101587866109860105) +* [DevEco Studio打开Codelabs示例报“Unknown host '不知道这样的主机。 (repo.ark.tools.huawei.com)'”错误的解决](https://developer.huawei.com/consumer/cn/forum/topic/0202700471997530007?fid=0101591351254000314) +* [#HarmonyOS技术训练营第三期#探探老婆在干嘛——通过HarmonyOS分布式文件获取对方手机内容](https://developer.huawei.com/consumer/cn/forum/topic/0203702299511520008?fid=0101591351254000314) +* [#HarmonyOS挑战赛第四期#使用ArkUI开发一个图片滑动播放功能HarmonyOS应用](https://developer.huawei.com/consumer/cn/forum/topic/0204705003425460081?fid=0101591351254000314) +* [#HarmonyOS征文#基于HarmonyOS ArkUI 3.0 框架,我成功开发了图片自动播放功能](https://developer.huawei.com/consumer/cn/forum/topic/0204705883233030129?fid=0101591351254000314) +* [什么叫做HarmonyOS“1+8+N”](https://developer.huawei.com/consumer/cn/forum/topic/0203715060368250074?fid=0101610563345550409) +* [基于HarmonyOS ArkUI 3.0 框架,开发了菜谱自动展播的应用](https://www.bilibili.com/video/BV1T34y1Z7X3/)(视频) +* [HarmonyOS离PC端有多远](https://developer.huawei.com/consumer/cn/forum/topic/0204726291294980583?fid=0101610563345550409) +* [HarmonyOS编程之路是知易行难,贵在坚持](https://developer.huawei.com/consumer/cn/forum/topic/0203727168139000447?fid=0101610563345550409) +* [15000积分兑换海思开发板Hi3518EV300 长啥样?!一起开箱看下,支持鸿蒙OS哦](https://developer.huawei.com/consumer/cn/forum/topic/0204729191116100676?fid=0103702273237500027) +* [OpenHarmony支持的系统类型以及对应的内核](https://developer.huawei.com/consumer/cn/forum/topic/0204729778972140718?fid=0103702273237490025) +* [解决DevEco Studio安装Ets SDK失败的问题](https://developer.huawei.com/consumer/cn/forum/topic/0201742704622820325?fid=26) +* [#HarmonyOS技术训练营第四期#使用ArkUI开发“仿WeLink打卡”HarmonyOS应用](https://developer.huawei.com/consumer/cn/forum/topic/0201742900461960432?fid=0101591351254000314) +* [使用ArkUI只需20行代码搞定“仿WeLink打卡”HarmonyOS应用](https://www.bilibili.com/video/BV15M4y1A7Vt/)(视频) +* [OpenHarmony HDC工具详解](https://developer.huawei.com/consumer/cn/forum/topic/0201763247640210925?fid=0103702273237520029) +* [华为开发者联盟社区2021年牛人之星](https://developer.huawei.com/consumer/cn/forum/topic/0201787443938940143) +* [以父之名·码力全开!写段HarmonyOS祝父亲节](https://developer.huawei.com/consumer/cn/forum/topic/0203909713809490254?fid=0101591351254000314) +* [#HarmonyOS技术训练营#AI来做HarmonyOS藏头诗应用](https://developer.huawei.com/consumer/cn/forum/topic/0204927609677810616?fid=0101591351254000314) +* [华为开发者联盟社区2022年度战码先锋1期开源贡献之星](https://developer.huawei.com/consumer/cn/forum/topic/0204957297101430449) +* [《鸿蒙生态应用开发白皮书》读后感,还是那熟悉的配方](https://developer.huawei.com/consumer/cn/forum/topic/0201103202674567076?fid=0101610563345550409) +* [#HarmonyOS体验官 玩转HarmonyOS 3必装DevEco Studio 3,注意避弹](https://developer.huawei.com/consumer/cn/forum/topic/0202103558349879153?fid=0101610563345550409) +* [#HarmonyOS体验官 用HarmonyOS ArkUI抽个盲盒头像](https://developer.huawei.com/consumer/cn/forum/topic/0202103570335932166?fid=0101591351254000314) +* [#HarmonyOS体验官【挑战赛第一期】用HarmonyOS ArkUI来开发一个购物应用程序](https://developer.huawei.com/consumer/cn/forum/topic/0202103750705507189?fid=0101587866109860105)、[视频](https://www.bilibili.com/video/BV1NG4y1o7rv/) +* [#HarmonyOS体验官【挑战赛第二期】用HarmonyOS ArkUI调用三方库PhotoView实现图片的联播、缩放](https://developer.huawei.com/consumer/cn/forum/topic/0202103760075502191?fid=0101591351254000314)、[视频](https://www.bilibili.com/video/BV1Rg411i7y1/) +* [#HarmonyOS体验官 【HarmonyOS ArkUI入门训练营】用HarmonyOS ArkUI来开发一个健康饮食应用](https://developer.huawei.com/consumer/cn/forum/topic/0202103820939502206?fid=0101591351254000314) +* [#HarmonyOS体验官【挑战赛第三期】用HarmonyOS ArkUI实现点赞美女翻牌动效](https://developer.huawei.com/consumer/cn/forum/topic/0201105240170004491) +* [#HarmonyOS体验官 HarmonyOS 3.1 Developer Preview新特性解读](https://developer.huawei.com/consumer/cn/forum/topic/0202105632577423558) +* [虽有HarmonyOS 3.1 Developer Preview,但想用3.0 Release版本怎么办?](https://developer.huawei.com/consumer/cn/forum/topic/0202105699819621571) +* [HarmonyOS ArkUI分布式数据服务开发](https://developer.huawei.com/consumer/cn/forum/topic/0201107105975570814) +* [HarmonyOS ArkUI关系型数据库开发](https://developer.huawei.com/consumer/cn/forum/topic/0201107109777137818) +* [HarmonyOS ArkUI首选项开发](https://developer.huawei.com/consumer/cn/forum/topic/0202107112558535813) +* [华为开发者联盟社区2022年牛人之星](https://developer.huawei.com/consumer/cn/forum/topic/0203109930647268095) +* [华为开发者联盟社区2022年度战码先锋2期开源贡献之星](https://developer.huawei.com/consumer/cn/forum/topic/0203110017942177099) +* [HarmonyOS应用在本地模拟器中运行](https://www.bilibili.com/video/BV1kG4y1Q765/)(视频) +* [HarmonyOS本地模拟器的使用](https://www.bilibili.com/video/BV1R84y1L783/)(视频) +* [HarmonyOS 3.1 Beta 1初体验,我在本地模拟器里面刷短视频](https://developer.huawei.com/consumer/cn/forum/topic/0204112795355581487) +* [HarmonyOS常用UI组件ToastDialog全面介绍及实操](https://developer.huawei.com/consumer/cn/forum/topic/0202115661867151329) +* [开发鸿蒙HarmonyOS版仿“抖音”App!](https://www.bilibili.com/video/BV1Fc411K7Uw/)(视频) +* [HarmonyOS版的“抖音”长啥样?有图有真相](https://developer.huawei.com/consumer/cn/forum/topic/0202118947187967814) +* [“鸿蒙系统实战短视频App 从0到1掌握HarmonyOS”实战课程已上线](https://developer.huawei.com/consumer/cn/blog/topic/03119094258031087) +* [开发鸿蒙HarmonyOS版仿“抖音”App-为什么学](https://www.bilibili.com/video/BV1gz4y1B744/)(视频) +* [开发鸿蒙HarmonyOS版仿“抖音”App-学什么](https://www.bilibili.com/video/BV1Do4y1g7Bm)(视频) +* [开发鸿蒙HarmonyOS版仿“抖音”App-怎么学](https://www.xiaohongshu.com/explore/647171c7000000001300d36c)(视频) +* [使用HarmonyOS ArkTS创建元服务(上)](https://developer.huawei.com/consumer/cn/forum/topic/0209123429587384205) +* [使用HarmonyOS ArkTS创建元服务(下)](https://developer.huawei.com/consumer/cn/forum/topic/0210123429701706203) +* [【以梦筑码 · 不负韶华】我伴随HarmonyOS一起成长](https://developer.huawei.com/consumer/cn/forum/topic/0208123985089505317?fid=0101587866109860105) +* [#HDC2023 心得分享#HarmonyOS应用开发的新机遇与挑战](https://developer.huawei.com/consumer/cn/forum/topic/0202126611589549374?fid=0101610563345550409) +* [基于HarmonyOS ArkUI实现音乐列表功能](https://developer.huawei.com/consumer/cn/forum/topic/0207128282721917372?fid=0101562279236410779) +* [基于HarmonyOS ArkUI实现七夕壁纸轮播](https://developer.huawei.com/consumer/cn/forum/topic/0209128602919619570?fid=0101591351254000314) +* [基于HarmonyOS低代码开发实现CPI图表](https://developer.huawei.com/consumer/cn/forum/topic/0207130176151650057?fid=0101591351254000314) +* [基于HarmonyOS ArkTS中秋国庆祝福程序](https://developer.huawei.com/consumer/cn/forum/topic/0201131193862897018?fid=0101591351254000314) +* [HarmonyOS 开发 Java 与 ArkTS 如何抉择](https://developer.huawei.com/consumer/cn/forum/topic/0203135735995709211?fid=0101610563345550409) +* [阿里入局鸿蒙!鸿蒙原生应用再添两员新丁](https://developer.huawei.com/consumer/cn/forum/topic/0203136159793226383?fid=0101610563345550409) +* [鸿蒙HarmonyOS应用新设备能跑老API开发的应用吗?](https://developer.huawei.com/consumer/cn/forum/topic/0204136335226928311?fid=0101610563345550409) +* [小红书已完成#鸿蒙原生应用#Beta版本开发](https://developer.huawei.com/consumer/cn/forum/topic/0204136336646227312?fid=0101610563345550409) +* [鸿蒙原生应用再添两员新丁!​B站、58入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0203136806231462640?fid=0101610563345550409) +* [鸿蒙原生应用再添新丁!高德地图入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0204136892240995558?fid=0101610563345550409) +* [鸿蒙原生应用再添新丁!麦当劳中国入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0204137287597648714?fid=0101610563345550409) +* [轻松掌握ArkTS!鸿蒙新作《鸿蒙HarmonyOS应用开发入门》简介](https://developer.huawei.com/consumer/cn/forum/topic/0204137371596828763?fid=0101610563345550409) +* [鸿蒙原生应用再添新丁!支付宝入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0201137460532479009?fid=0101610563345550409) +* [轻松掌握ArkTS!鸿蒙新作《鸿蒙HarmonyOS应用开发入门》开箱](https://www.bilibili.com/video/BV1ee411o7XE)(视频) +* [鸿蒙原生应用再添新丁!同花顺入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0201137715493130041?fid=0101610563345550409) +* [鸿蒙原生应用再添新丁!网易游戏入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0204138063180278909?fid=0101610563345550409) +* [鸿蒙原生应用再添新丁!米哈游入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0202138316368453170) +* [【画龙迎春】纯血鸿蒙来画龙!基于HarmonyOS ArkTS来操作SVG图片](https://developer.huawei.com/consumer/cn/forum/topic/0203143920386713714) +* [当新版DevEco Studio打开老版HarmonyOS应用报错解决](https://www.bilibili.com/video/BV1bC411n7Lr/)(视频) +* [“2024鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)”实战课程已上线](https://waylau.com/harmonyos-short-video-arkts/) +* [ArkTS开发原生鸿蒙HarmonyOS短视频应用](https://www.bilibili.com/video/BV1KD421M7a8/)(视频) +* [2024鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)-课程导学](https://www.bilibili.com/video/BV11D421K7Cd/)(视频) +* [HarmonyOS 3.1/4.0应用升级到HarmonyOS NEXT改动点](https://waylau.com/changes-upgrade-from-3-1-4-0-to-harmonyos-next-developer-preview2) +* [DevEco Studio NEXT Developer Beta3打开NEXT Developer Preview2应用报错问题解决](https://waylau.com/upgrade-from-harmonyos-next-developer-preview2-to-next-developer-beta3/) +* [HarmonyOS NEXT仓颉编程语言开发环境搭建(安装DevEco Studio Cangjie Plugin)](https://waylau.com/install-deveco-studio-cangjie-plugin/) +* [HarmonyOS ArkTS用户首选项的开发及测试](https://developer.huawei.com/consumer/cn/forum/topic/0212162121584279891?fid=0109140870620153026) +* [HarmonyOS NEXT Release版本发布](https://developer.huawei.com/consumer/cn/forum/topic/0202163790773265911?fid=0109140870620153026) +* [原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布](https://developer.huawei.com/consumer/cn/forum/topic/0201165020167549746?fid=0109140870620153026) +* [DevEco Studio 5.0.1 Beta3安装及配置](https://developer.huawei.com/consumer/cn/forum/topic/0201167060557489345) +* [HarmonyOS 3.1/4项目在DevEco Studio 5.0(HarmonyOS NEXT)版本下使用的问题](https://waylau.com/deveco-studio-5-develop-harmonyos-3/) +* [《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》简介](https://waylau.com/about-harmonyos-application-development-from-zero-to-hero-2nd-edition-book/) +* [仓颉开发HarmonyOS,报错error: undeclared identifier 'ViewStackProcessor'](https://developer.huawei.com/consumer/cn/forum/topic/0203168734455974805) +* [仓颉开发HarmonyOS,@Builder 和@Component的区别?](https://developer.huawei.com/consumer/cn/forum/topic/0201168738273407796) +* [仓颉开发HarmonyOS,internal import 和import 有什么区别? ](https://developer.huawei.com/consumer/cn/forum/topic/0201168738630513798) +* [市面上最厚的鸿蒙著作!《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》](https://www.bilibili.com/video/BV1N2ktYeEBd/)(视频) +* [HarmonyOS产生的背景](https://developer.huawei.com/consumer/cn/forum/topic/0203170498345755621?fid=0109140870620153026) +* [HarmonyOS技术理念](https://developer.huawei.com/consumer/cn/forum/topic/0202170671991846041?fid=0109140870620153026) +* [《鸿蒙之光HarmonyOS NEXT原生应用开发入门》简介](https://waylau.com/about-harmonyos-next-tutorial-book/) +* [HarmonyOS架构介绍](https://developer.huawei.com/consumer/cn/forum/topic/0202171014436926490) +* [纯血鸿蒙!《鸿蒙之光HarmonyOS NEXT原生应用开发入门》](https://www.bilibili.com/video/BV11cCpYXEyL/)(视频) +* [HarmonyOS纯血鸿蒙新特性](https://developer.huawei.com/consumer/cn/forum/topic/0204171533399971963?fid=0109140870620153026) +* [HarmonyOS环境搭建之注册华为开发者联盟帐号](https://developer.huawei.com/consumer/cn/forum/topic/0202172141875455029?fid=0109140870620153026) +* [DevEco Studio 3.1/4 下载安装](https://developer.huawei.com/consumer/cn/forum/topic/0202172750026769021) +* [DeepSeek火出圈,国货当自强!HarmonyOS就是原生智能系统](https://developer.huawei.com/consumer/cn/forum/topic/0203174136422203503?fid=0109140870620153026) +* [原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力](https://www.imooc.com/article/378306) +* [鸿蒙原生应用再添新丁!北京广电、充电管家、有我帮 入局鸿蒙](https://developer.huawei.com/consumer/cn/forum/topic/0203175158162669774?fid=0109140870620153026) +* [HarmonyOS使用系统图标](https://www.imooc.com/article/378837) +* [HarmonayOS通过应用链接拉起指定应用](https://waylau.com/harmonyos-open-app-with-deep-linking/) +* [华为鸿蒙PC要来了?](https://developer.huawei.com/consumer/cn/blog/topic/03177612448714040) +* [“HarmonyOS NEXT+AI大模型打造智能助手APP(仓颉版)”实战课程简介](https://waylau.com/develop-native-harmonyos-ai-assistant-with-cangjie-video/) +* [HarmonyOS 5 时代已来!华为首款阔折叠屏 Pura X 发布,开发者如何抢占未来生态红利?](https://developer.huawei.com/consumer/cn/forum/topic/0207177872217215165?fid=0109140870620153026) +* [如何亲手打造AI智能助手APP?项目效果演示](https://www.bilibili.com/video/BV15zXeYfEdY)(视频) +* [仓颉编程语言还能这么玩?HarmonyOS NEXT+AI大模型打造智能助手APP](https://developer.huawei.com/consumer/cn/forum/topic/0204178189326184176?fid=0109140870620153026) +* [谷歌将 Android OS 转为 “内部开发”,对鸿蒙系统来说是否是个机会?](https://developer.huawei.com/consumer/cn/forum/topic/0204178542373267294?fid=0109140870620153026) +* [人民日报批安卓假开源真垄断,鸿蒙系统或转正](https://www.imooc.com/article/379847) +* [华为2024年报:鸿蒙生态正在取得历史性突破](https://developer.huawei.com/consumer/cn/forum/topic/0204178880836982377?fid=0109140870620153026) +* [鸿蒙生态日日新:“郑好办”鸿蒙版上线,今日头条、人民日报等更新](https://developer.huawei.com/consumer/cn/blog/topic/03179484239808023) +* [【HarmonyOS NEXT+AI】问答01:课程里用的什么大模型?能用DeepSeek吗?](https://developer.huawei.com/consumer/cn/forum/topic/0202179567792367460?fid=0109140870620153026) +* [科技巨头加倍押注生成式 AI](https://developer.huawei.com/consumer/cn/forum/topic/0207179667509011509?fid=23) +* [【HarmonyOS NEXT+AI】问答02:有一点编程基础,可以学不?](https://developer.huawei.com/consumer/cn/forum/topic/0207179750550660521?fid=0109140870620153026) +* [【HarmonyOS NEXT+AI】问答03:找不到DevEco Studio Cangjie Plugin下载链接?](https://developer.huawei.com/consumer/cn/forum/topic/0207180264471291591?fid=0109140870620153026) +* [【HarmonyOS NEXT+AI】问答04:仓颉编程语言适合毕业设计吗? +](https://developer.huawei.com/consumer/cn/forum/topic/0201181154070287038?fid=0109140870620153026) +* [#我的鸿蒙开发手记#从0到1如何踏上HarmonyOS之旅](https://ost.51cto.com/posts/33218) +* [华为首款鸿蒙电脑正式亮相](https://developer.huawei.com/consumer/cn/forum/topic/0202182086290078559?fid=0109140870620153026) +* [【HarmonyOS NEXT+AI】问答05:ArkTS和仓颉编程语言怎么选?](https://developer.huawei.com/consumer/cn/forum/topic/0204182208074587445?fid=0109140870620153026) +* [HarmonyOS NEXT+AI打造智能助手APP(适配DeepSeek)](https://developer.huawei.com/consumer/cn/forum/topic/0201182544745864038?fid=0109140870620153026) +* [鸿蒙电脑的诞生是国产操作系统的破壁之战](https://developer.huawei.com/consumer/cn/forum/topic/0202183593464049037?fid=0109140870620153026) +* [给鸿蒙PC和胖折叠Pura X写程序是什么体验](https://www.bilibili.com/video/BV1AE76z5Ei8/)(视频) +* [API 15实战案例:新晋导航点组件Indicator详解](https://harmonyosdev.csdn.net/683c14e3965a29319f248ec0.html) +* [HarmonyOS API 15 Swiper动效模式SwiperAnimationMode应用展示](https://developer.huawei.com/consumer/cn/forum/topic/0207186048437968004?fid=0109140870620153026) +* [华为自研仓颉编程语言将开源,未来与ArkTS同等地位](https://developer.huawei.com/consumer/cn/forum/topic/0210186139141263055?fid=0109140870620153026) +* [【HarmonyOS NEXT+AI】问答06:仓颉编程语言的仓颉工具链和DevEco Studio Cangjie Plugin是什么关系?](https://www.imooc.com/article/382914) +* [仓颉编程语言(Cangjie)正式发布1.0.0 LTS版本,附安装配置教程](https://developer.huawei.com/consumer/cn/forum/topic/0202187267416126149?fid=0109140870620153026) +* [仓颉编程语言(Cangjie)正式发布1.0.0 LTS版本,附安装配置教程](https://www.bilibili.com/video/BV1mz36zMEdz/)(视频) +* [安装仓颉编程语言(Cangjie)1.0.0版本VS Code插件](https://www.bilibili.com/video/BV1DGGszeEK2/)(视频) +* [HarmonyOS ArkTS关系型数据库开发](https://www.imooc.com/article/383246) +* [HarmonyOS ArkTS 获取位置服务](https://waylau.com/harmonyos-arkts-geo-location-manager) +* [HarmonyOS Navigation实现导航与路由切换](https://waylau.com/harmonyos-navigation/) +* [鸿蒙HamonyOS应用上架手动签名与发布](https://developer.huawei.com/consumer/cn/forum/topic/0201193916382361313?fid=0109140870620153026) +* [如何将HarmonyOS 5应用升级到HarmonyOS 6](https://waylau.com/upgrade-harmonyos-app-version-from-5-to-6/) +* [如何将HarmonyOS 5应用升级到HarmonyOS 6](https://www.bilibili.com/video/BV1yBHwzDEkK/)(视频) +* [幕连实现手机无线投屏到电脑](https://www.bilibili.com/video/BV1bXH5zfEiw/)(视频) +* [华为电脑管家助力华为手机与PC多屏协同](https://www.bilibili.com/video/BV1MM4FzwEBS/)(视频) +* [非华为电脑安装华为电脑管家](https://www.bilibili.com/video/BV1YnxBzCEmL/)(视频) +* [【HarmonyOS NEXT+AI】问答07:DevEco Studio Cangjie Plugin在哪里下载?为什么看不到?](https://developer.huawei.com/consumer/cn/forum/topic/0201195374935980891?fid=0109140870620153026) +* [鸿蒙6要来了,全网首发HarmonyOS 6 AI开发教程上线啦!](https://www.bilibili.com/video/BV1wCWxzpEsq/)(视频) +* [全网首发鸿蒙6 AI教程“HarmonyOS 6 AI应用开发”上线啦!](https://waylau.com/the-first-harmonyos-6-ai-tutorial-is-now-available-online/) +* [鸿蒙操作系统6特别发布,HarmonyOS 5终端设备数量突破2300万!](https://www.bilibili.com/video/BV1qMs8zdESm/)(视频) +* [鸿蒙操作系统6特别发布,HarmonyOS 5终端设备数量突破2300万!](https://ost.51cto.com/posts/36792) +* [HarmonyOS Navigation嵌套Tabs导致Tabs布局异常问题解决](https://waylau.com/nested-tabs-in-harmonyos-navigation/) +* [HarmonyOS通过openLink拉起浏览器打开链接](https://waylau.com/harmonyos-launches-a-browser-to-open-a-link-through-openlink) +* [HarmonyOS通过wifiManager来连接Wi-Fi](https://waylau.com/harmonyos-wifi-manager-connect-to-wifi/) +* [拥抱鸿蒙生态:从入门到精通的优质学习资料合集](https://developer.huawei.com/consumer/cn/forum/topic/0202200313895279180?fid=0109140870620153026) +* [拥抱鸿蒙生态,优质学习资料合集](https://www.bilibili.com/video/BV18s2VBiEfM/)(视频) +* [华为鸿蒙极客2025证书开箱](https://www.bilibili.com/video/BV1pNmcBREYP/)(视频) +* [《鸿蒙之光HarmonyOS 6应用开发入门》开箱](https://www.bilibili.com/video/BV1b1rwBREbk/)(视频) +* [2026新年首发《鸿蒙之光HarmonyOS 6应用开发入门》简介](https://waylau.com/about-harmonyos-6-tutorial-book/) +* [首本鸿蒙架构师培养手册《鸿蒙架构师修炼之道》开箱](https://www.bilibili.com/video/BV1hw6TBiEgQ)(视频) +* [首本鸿蒙架构师培养手册《鸿蒙架构师修炼之道》简介](https://waylau.com/about-the-cultivation-of-harmonyos-architect-book/) +* [鸿蒙架构师修炼之道-架构师的职责是什么?](https://developer.huawei.com/consumer/cn/forum/topic/0201205677415918678?fid=0109140870620153026) +* [鸿蒙架构师修炼之道-如何成为团队的架构师](https://developer.huawei.com/consumer/cn/forum/topic/0201206293717762965?fid=0109140870620153026) +* [鸿蒙架构师修炼之道-架构师设计思维特点](https://www.imooc.com/article/389036) +* [2026 年鸿蒙生态的几本好书分享](https://developer.huawei.com/consumer/cn/forum/topic/0203207492082552137?fid=0109140870620153026 +* [从开发者到架构师:一本打通鸿蒙全栈的成长地图](https://www.imooc.com/article/389333) +* [鸿蒙架构师修炼之道-架构师核心思维方式](https://www.imooc.com/article/389543) +* [鸿蒙架构师修炼之道-关键要素](https://www.imooc.com/article/389863) +* [鸿蒙架构师修炼之道-实践应用](https://www.imooc.com/article/390159) +* [鸿蒙架构师修炼之道-什么是软件架构](https://developer.huawei.com/consumer/cn/forum/topic/0203210415927615786?fid=0109140870620153026) * 未完待续... -## Samples 示例 +## Samples 示例(按发表时间升序排序) * [Hello World](samples/HelloWorld) * [多个AbilitySlice之间的路由与导航](samples/AbilitySliceNavigation) @@ -35,11 +220,15 @@ * [DataAbilityHelper访问文件](samples/DataAbilityHelperAccessFile) * [DataAbilityHelper访问数据库](samples/DataAbilityHelperAccessDatabase) * [多个Page之间的路由与导航](samples/IntentOperationWithAction) +* [启动系统应用拨号盘](samples/IntentOperationWithActionDial) +* [跳转到应用管理](samples/IntentOperationWithActionManageApplicationsSettings) +* [跳转到搜索](samples/IntentOperationWithActionSearch) +* [跳转到指定URI进行访问](samples/IntentOperationWithUri) * [分布式任务调度启动远程FA](samples/DistributedSchedulingStartRemoteFA) * [分布式任务调度启动和关闭远程PA](samples/DistributedSchedulingStartStopRemotePA) -* [公共事件服务发布事件](samples/CommonEventPublisher)(test) -* [公共事件服务订阅事件](samples/CommonEventSubscriber)(test) -* [高级通知服务](samples/Notification)(test) +* [公共事件服务发布事件](samples/CommonEventPublisher) +* [公共事件服务订阅事件](samples/CommonEventSubscriber) +* [高级通知服务](samples/Notification) * [剪切板数据的写入](samples/SystemPasteboardSetter) * [剪切板数据的读取](samples/SystemPasteboardGetter) * [XML创建布局](samples/DirectionalLayoutWithXml) @@ -48,6 +237,7 @@ * [常用显示类组件——Image](samples/Image) * [常用显示类组件——ProgressBar](samples/ProgressBar) * [常用交互类组件——Button](samples/Button) +* [常用交互类组件——Slider](samples/Slider) * [常用交互类组件——TextField](samples/TextField) * [常用交互类组件——Checkbox](samples/Checkbox) * [常用交互类组件——RadioButton/RadioContaine](samples/RadioButtonRadioContaine) @@ -57,13 +247,19 @@ * [常用交互类组件——Picker](samples/Picker) * [常用交互类组件——ListContainer](samples/ListContainer) * [常用交互类组件——RoundProgressBar](samples/RoundProgressBar) +* [常用交互类组件——PageSlider](samples/PageSlider) +* [常用交互类组件——CommonDialog](samples/CommonDialog) +* [常用交互类组件——ToastDialog](samples/ToastDialog) * [常用布局——DirectionalLayout](samples/DirectionalLayout) * [常用布局——DependentLayout](samples/DependentLayout) * [常用布局——StackLayout](samples/StackLayout) * [常用布局——TableLayout](samples/TableLayout) +* [常用布局——PositionLayout](samples/PositionLayout) +* [常用布局——AdaptiveBoxLayout](samples/AdaptiveBoxLayout) * [创建JS FA应用](samples/JsFa) * [点赞按钮](samples/GiveLike) * [JS FA调用PA](samples/JsFaCallPa) +* [JS UI之Chart、 Switch组件的组合使用](samples/JsChartSwitch) * [多模输入事件](samples/MultimodalEvent) * [线程管理示例](samples/ParallelTaskDispatcher) * [线程间通信示例](samples/EventHandler) @@ -74,7 +270,7 @@ * [图像编解码](samples/ImageCodec) * [位图操作](samples/PixelMap) * [图像属性解码](samples/ImageSourceExifUtils) -* [相机设备创建、配置、帧捕获](samples/CameraKit) +* [相机设备创建、配置、帧捕获](samples/CameraKit)(test) * [音频播放](samples/AudioRenderer)(test) * [音频采集](samples/AudioCapturer)(test) * [短音播放](samples/SoundPlayer)(test) @@ -90,10 +286,10 @@ * [BLE扫描和广播](samples/BleCentralManager)(test) * [WLAN基础功能](samples/WifiDevice) * [不信任热点配置](samples/WifiDeviceUntrustedConfig) -* [WLAN消息通知](samples/WifiEventSubscriber) -* [使用当前网络打开一个URL链接](samples/NetManagerHandleURL) +* [WLAN消息通知](samples/WifiEventSubscriber)(test) +* [使用当前网络打开一个URL链接](samples/NetManagerHandleURL)(test) * [使用当前网络进行Socket数据传输](samples/NetManagerHandleSocket) -* [流量统计](samples/DataFlowStatistics) +* [流量统计](samples/DataFlowStatistics)(test) * [获取当前蜂窝网络信号信息](samples/RadioInfoManager) * [观察蜂窝网络状态变化](samples/RadioStateObserver) * [传感器示例](samples/CategoryOrientationAgent) @@ -103,6 +299,10 @@ * [使用对象关系映射数据库](samples/DataAbilityHelperAccessORM) * [使用轻量级偏好数据库](samples/Preferences) * [使用数据存储管理](samples/DataUsage) +* [原子化服务HelloDog](samples/HelloDog) +* [创建服务卡片](samples/AbilityServiceWidget) +* [设备迁移及回迁](samples/ContinueRemoteFA) +* [多设备协同](samples/ContinueRemoteFACollaboration) * [ElectronicAlbum](samples/ElectronicAlbum) * [KlotskiJs](samples/KlotskiJs) * [AudioPlayer](samples/AudioPlayer)(TODO) @@ -111,9 +311,111 @@ * [Tetris](samples/Tetris) * [Swipe](samples/SwipeJs)(test) * [Todo](samples/Todo)(TODO) +* [LiuweiweiNewsDetails](samples/LiuweiweiNewsDetails)(TODO) +* [LiuweiweiImageHandler](samples/LiuweiweiImageHandler)(TODO) +* [LiuweiweiAiImageSearch](samples/LiuweiweiAiImageSearch)(TODO) +* [仿抖音短视频应用](samples/Douyin) +* [ArkUI开发一个图片滑动播放功能](samples/EtsUISwiper) +* [分布式文件共享](samples/DistributedFile) +* [ArkUI开发一个图片自动播放功能](samples/EtsUISwiperAutoPlay) +* [使用ArkUI开发“仿WeLink打卡”](samples/WeLinkPunchCard)(API 6.0.0(20)) +* [使用ArkUI开发“父亲节的祝福”](samples/FatherDay) +* [AI来做HarmonyOS藏头诗](samples/WaylauAcrosticPoem) +* [ArkUI抽个盲盒头像](samples/ArkUIExperience) +* [ArkUI 购物应用](samples/ArkUIShopping) +* [ArkUI调用三方库PhotoView](samples/ArkUIThirdPartyLibrary) +* [ArkUI健康饮食应用](samples/ArkUIHealthyDiet) +* [ArkUI点赞美女翻牌](samples/GiveThumbsUp) +* [ArkUI HelloWorld](samples/ArkUIHelloWorld) +* [ArkUI 内页面的跳转和数据传递](samples/ArkUIPagesRouter) +* [ArkUI 显式Want启动Ability](samples/ArkUIWantStartAbility) +* [ArkUI 隐式Want打开网址](samples/ArkUIWantOpenURI)(Test) +* [ArkUI 隐式Want打开应用管理](samples/ArkUIWantOpenManageApplications) +* [ArkUI 登录界面](samples/ArkUILogin)(API 6.0.0(20)) +* [ArkTS 公共事件](samples/ArkTSCommonEventService)(API 6.0.0(20)) +* [ArkTS 图片编解码](samples/ArkTSImageCodec)(test) +* [ArkTS 窗口开发](samples/ArkTSWindow) +* [ArkTS HTTP请求数据](samples/ArkTSHttp)(API 6.0.0(20)) +* [ArkTS Web组件](samples/ArkTSWebComponent)(API 6.0.0(20)) +* [ArkTS 用户授权](samples/ArkTSUserGrant)(API 6.0.0(20)) +* [ArkTS 分布式数据服务开发](samples/ArkTSDistributedData)(API 6.0.0(20)) +* [ArkTS 关系型数据库开发](samples/ArkTSRdb)(API 6.0.0(20)) +* [ArkTS 首选项开发](samples/ArkTSPreferences)(API 6.0.0(20)) +* [ArkUI 基础组件开发](samples/ArkUIBasicComponents)(API 6.0.0(20)) +* [ArkUI 容器组件开发](samples/ArkUIContainerComponents)(API 6.0.0(20)) +* [ArkUI 媒体组件开发](samples/ArkUIMediaComponents)(API 6.0.0(20)) +* [ArkUI Canvas组件开发](samples/ArkUICanvasComponents)(API 6.0.0(20)) +* [ArkUI 计算器](samples/ArkUICalculator)(API 6.0.0(20)) +* [ArkTS 视频播放器](samples/ArkTSVideoPlayer) +* [ArkUI 仿微信应用](samples/ArkUIWeChat) +* [ArkTS 元服务](samples/ArkTSAtomicService) +* [ArkTS 音乐播放器](samples/ArkTSMusicPlayer) +* [ArkUI 七夕壁纸轮播](samples/ArkUIExpressingLove) +* [ArkTS 低代码开发实现CPI图表](samples/ArkTSCPIChart) +* [ArkUI 中秋国庆祝福程序](samples/ArkUIMidAutumnFestival) +* [基于HarmonyOS ArkTS来操作SVG图片](samples/ArkTSSVGChineseLoong) +* [ArkTS 统计字符串的字符数](samples/CountTheNumberOfCharacters)(API 6.0.0(20)) +* [ArkTS 录音机](samples/ArkTSAudioCapturer) +* [ArkTS 音乐播放器](samples/ArkTSAVPlayer)(API 6.0.0(20)) +* [ArkTS 使用Emitter进行线程间通信](samples/ArkTSEmitter)(API 6.0.0(20)) +* [ArkTS 实现图片查看器UI原型](samples/ArkTSMultiPictureUI)(API 6.0.0(20)) +* [ArkTS 图片查看器](samples/ArkTSMultiPicture) +* [ArkTS 购物车](samples/ArkTSShoppingCart)(API 6.0.0(20)) +* [ArkTS 创建子窗口](samples/ArkTSSubWindow)(API 6.0.0(20)) +* [ArkTS 使用麦克风](samples/ArkTSUserGrantMicrophone)(API 6.0.0(20)) +* [ArkTS 启动系统设置](samples/ArkTSWantOpenSetting)(API 6.0.0(20)) +* [ArkTS 显式Want启动Ability](samples/ArkTSWantStartAbility)(API 6.0.0(20)) +* [ArkTS Web组件展示HTML页面](samples/ArkTSWebComponentHTML)(API 6.0.0(20)) +* [ArkTS 实现窗口沉浸式效果](samples/ArkTSWindowLayoutFullScreen)(API 6.0.0(20)) +* [ArkUI 绘制组件](samples/ArkUIDrawingComponents)(API 6.0.0(20)) +* [ArkUI 图片轮播播放器](samples/ArkUISwiper)(API 6.0.0(20)) +* [ArkTS Swiper动效模式SwiperAnimationMode](samples/ArkTSSwiperAnimationMode) +* [ArkTS 导航点组件](samples/ArkTSIndicator) +* [ArkTS 获取位置服务](samples/ArkTSGeoLocationManager) +* [ArkTS 组件导航Navigation](samples/ArkTSNavigation)(API 6.0.0(20)) +* [ArkTS openLink打开网址](samples/ArkTSOpenLink)(API 6.0.0(20)) +* [ArkTS 连接Wi-Fi](samples/ArkTSWifiManagerConnectToWifi)(API 6.0.0(20)) +* [ArkTS Hello World](samples/ArkTSHelloWorld)(API 6.0.0(20)) +* [ArkTS AI扫描](samples/AIScanner)(API 6.0.0(20)) +* [ArkTS 语音识别](samples/ArkTSCoreSpeechSpeechRecognizer)(API 6.0.0(20)) +* [ArkTS 视频AI字幕](samples/ArkTSSpeechAICaption)(API 6.0.0(20)) +* [仓颉 Hello World](samples/CangjieHelloWorld)(API 5.0.2(14)) +* [ArkUI 使用系统图标](samples/ArkUISymbolGlyphSymbolSpan)(API 5.0.2(14)) +* [ArkTS 通过应用链接拉起指定应用](samples/ArkTSDeepLinkingStartup)(API 5.0.2(14)) +* [ArkTS 通过应用链接拉起指定应用](samples/ArkTSDeepLinkingTarget)(API 5.0.2(14)) +* [ArkTS 使用EventHub进行线程内通信](samples/ArkTSEventHub)(API 5.0.2(14)) +* [ArkTS 异步并发](samples/ArkTSPromise)(API 5.0.2(14)) +* [ArkTS 使用TaskPool](samples/ArkTSTaskPool)(API 5.0.2(14)) +* [ArkTS 使用Worker](samples/ArkTSWorker)(API 5.0.2(14)) +* [ArkTS TaskPool任务与宿主线程通信](samples/ArkTSInterThreadCommunicationTaskPool)(API 5.0.2(14)) +* [ArkTS HiLog开发示例](samples/ArkTSHiLog)(API 5.0.2(14)) +* [ArkTS HiAppEvent使用示例](samples/ArkTSHiAppEvent)(API 5.0.2(14)) +* [ArkTS HiTraceMeter使用示例](samples/ArkTSHiTraceMeter)(API 5.0.2(14)) +* [ArkTS HiTraceChain使用示例](samples/ArkTSHiTraceChain)(API 5.0.2(14)) * 未完待续... +以下是部分示例运行界面。 + +![ArkTS 音乐播放器](images/ArkTSAVPlayer.png) + + +![ArkTS 仿微信应用](images/ArkUIWeChat.png) + + +![视频播放器](images/VedioPlayer.png) + + +![俄罗斯方块](images/Tetris.png) + +![购物应用](images/ArkUIShopping.png) + +![ArkTS 图片查看器](images/ArkTSMultiPicture.png) + +![ArkTS 短视频应用](images/harmonyos-short-video-arkts.png) + +![仓颉智能AI助手](images/cangjie-harmonyos-ai.png) + ## Get start 如何开始阅读 选择下面入口之一: @@ -126,6 +428,38 @@ 书中所有示例源码,移步至的 `samples` 目录下,代码遵循《[Java 编码规范]()》 + +## Book 配套书籍、课程 + +HarmonyOS配套练习题库,见“[HarmonyOS题库](https://github.com/waylau/harmonyos-exam)”。 + +如果你喜欢本开源书,也欢迎支持下该书的正式出版物,实体店及各大网店有售。 + +* [《鸿蒙HarmonyOS手机应用开发实战》](https://waylau.com/about-harmonyos-mobile-application-development-book)(清华大学出版社) + * [京东](https://search.jd.com/Search?keyword=%E6%9F%B3%E4%BC%9F%E5%8D%AB%20%E9%B8%BF%E8%92%99HarmonyOS%E6%89%8B%E6%9C%BA%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98&enc=utf-8&wq=%E6%9F%B3%E4%BC%9F%E5%8D%AB%20%E9%B8%BF%E8%92%99HarmonyOS%E6%89%8B%E6%9C%BA%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%AE%9E%E6%88%98&pvid=0a1bb438769f490f9795b135278138ea) + * [当当](http://search.dangdang.com/?key=%C1%F8%CE%B0%CE%C0%20%BA%E8%C3%C9HarmonyOS%CA%D6%BB%FA%D3%A6%D3%C3%BF%AA%B7%A2%CA%B5%D5%BD&act=input) +* [《鸿蒙HarmonyOS应用开发从入门到精通》](https://waylau.com/about-harmonyos-application-development-from-zero-to-hero-book/)(北京大学出版社) + * [京东](https://item.jd.com/13696724.html) + * [当当](http://product.dangdang.com/29386650.html) +* [鸿蒙系统实战短视频App 从0到1掌握HarmonyOS](https://coding.imooc.com/class/674.html)(视频) +* [《鸿蒙HarmonyOS应用开发入门》](https://waylau.com/about-harmonyos-3-tutorial-book/)(清华大学出版社) + * [京东](https://item.jd.com/13963157.html) + * [当当](https://product.dangdang.com/29664217.html) +* [鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)](https://coding.imooc.com/class/843.html)(视频) +* [《鸿蒙HarmonyOS应用开发从入门到精通(第2版)》](https://waylau.com/about-harmonyos-application-development-from-zero-to-hero-2nd-edition-book/)(北京大学出版社) + * [京东](https://item.jd.com/14349963.html) + * [当当](http://product.dangdang.com/29821274.html) +* [《鸿蒙之光HarmonyOS NEXT原生应用开发入门》](https://waylau.com/about-harmonyos-next-tutorial-book/)(清华大学出版社) + * [京东](https://item.jd.com/14905890.html) + * [当当](https://product.dangdang.com/29832721.html) +* [HarmonyOS NEXT+AI大模型打造智能助手APP(仓颉版)](https://coding.imooc.com/class/927.html)(视频) +* [HarmonyOS 6 AI应用开发](https://edu.51cto.com/course/39601.html)(视频) +* [鸿蒙之光HarmonyOS 6应用开发入门](https://waylau.com/about-harmonyos-6-tutorial-book/)(清华大学出版社) + * [京东](https://item.jd.com/10208282739020.html) + * [当当](https://search.dangdang.com/?key=%BA%E8%C3%C9%D6%AE%B9%E2HarmonyOS%206%D3%A6%D3%C3%BF%AA%B7%A2%C8%EB%C3%C5&act=input) +* [鸿蒙架构师修炼之道](https://waylau.com/about-the-cultivation-of-harmonyos-architect-book/)(北京大学出版社) + * [京东](https://item.jd.com/15299684.html) + * [当当](https://product.dangdang.com/30005979.html) ## Issue 意见、建议 如有勘误、意见或建议欢迎拍砖 @@ -138,6 +472,65 @@ * Twitter: [waylau521](https://twitter.com/waylau521) * Github : [waylau](https://github.com/waylau) +## Certificate 作者荣誉 + +华为开发者联盟社区2021牛人之星 + +![](images/huawei-developer-niuren-certificate.png) + +HarmonyOS技术达人、博客内测体验官、博客之星 + +![](images/harmonyos-certificate.png) + +2022年度战码先锋1期开源贡献之星 + +![](images/openharmony-certificate.png) + + +华为开发者联盟社区2022牛人之星 + +![](images/huawei-developer-niuren-certificate-2022.jpg) + + +2022年度战码先锋2期开源贡献之星 + +![](images/openharmony-certificate-2.jpg) + + +HarmonyOS应用开发者高级认证 + +![](images/harmonyos-application-developer-advanced-certification.png) + +2024年度卓越贡献著译者 + +![](images/2024-outstanding-contribution-author.png) + +2024计算机类畅销新书奖 + +![](images/2024-computer-bestselling-new-book-award.png) + +HUAWEI Developer Experts(HDE) + +![](images/huawei-developer-experts.png) + + +HarmonyOS学习资源创作达人(第一期) + +![](images/harmonyos-learning-resource-creator.jpg) + +HarmonyOS学习资源创作达人(第二期) + +![](images/harmonyos-learning-resource-creator-2nd.jpg) + + +鸿蒙极客2025 + +![](images/harmonyos-geek-2025.jpg) + + +清华大学出版社2025年度星选荐书人 + +![](images/tsinghua-university-press-2025-star-book-recommender.jpg) ## Support Me 请老卫喝一杯 diff --git a/images/2024-computer-bestselling-new-book-award.png b/images/2024-computer-bestselling-new-book-award.png new file mode 100644 index 00000000..db3b519b Binary files /dev/null and b/images/2024-computer-bestselling-new-book-award.png differ diff --git a/images/2024-outstanding-contribution-author.png b/images/2024-outstanding-contribution-author.png new file mode 100644 index 00000000..55d5ea23 Binary files /dev/null and b/images/2024-outstanding-contribution-author.png differ diff --git a/images/AbilityServiceWidget.png b/images/AbilityServiceWidget.png new file mode 100644 index 00000000..986d2a93 Binary files /dev/null and b/images/AbilityServiceWidget.png differ diff --git a/images/ArkTSAVPlayer.png b/images/ArkTSAVPlayer.png new file mode 100644 index 00000000..1a395c01 Binary files /dev/null and b/images/ArkTSAVPlayer.png differ diff --git a/images/ArkTSMultiPicture.png b/images/ArkTSMultiPicture.png new file mode 100644 index 00000000..6a8e825a Binary files /dev/null and b/images/ArkTSMultiPicture.png differ diff --git a/images/ArkUIShopping.png b/images/ArkUIShopping.png new file mode 100644 index 00000000..c8a1204e Binary files /dev/null and b/images/ArkUIShopping.png differ diff --git a/images/ArkUIWeChat.png b/images/ArkUIWeChat.png new file mode 100644 index 00000000..a5089529 Binary files /dev/null and b/images/ArkUIWeChat.png differ diff --git a/images/Tetris.png b/images/Tetris.png new file mode 100644 index 00000000..fdd43256 Binary files /dev/null and b/images/Tetris.png differ diff --git a/images/VedioPlayer.png b/images/VedioPlayer.png new file mode 100644 index 00000000..23135d25 Binary files /dev/null and b/images/VedioPlayer.png differ diff --git a/images/cangjie-harmonyos-ai.png b/images/cangjie-harmonyos-ai.png new file mode 100644 index 00000000..19f4c5bc Binary files /dev/null and b/images/cangjie-harmonyos-ai.png differ diff --git a/images/harmonyOS_logo.png b/images/harmonyOS_logo.png new file mode 100644 index 00000000..404b084f Binary files /dev/null and b/images/harmonyOS_logo.png differ diff --git a/images/harmonyos-application-developer-advanced-certification.png b/images/harmonyos-application-developer-advanced-certification.png new file mode 100644 index 00000000..ffa15fa5 Binary files /dev/null and b/images/harmonyos-application-developer-advanced-certification.png differ diff --git a/images/harmonyos-certificate.png b/images/harmonyos-certificate.png new file mode 100644 index 00000000..275ab92b Binary files /dev/null and b/images/harmonyos-certificate.png differ diff --git a/images/harmonyos-geek-2025.jpg b/images/harmonyos-geek-2025.jpg new file mode 100644 index 00000000..cb4be2a7 Binary files /dev/null and b/images/harmonyos-geek-2025.jpg differ diff --git a/images/harmonyos-learning-resource-creator-2nd.jpg b/images/harmonyos-learning-resource-creator-2nd.jpg new file mode 100644 index 00000000..931a0783 Binary files /dev/null and b/images/harmonyos-learning-resource-creator-2nd.jpg differ diff --git a/images/harmonyos-learning-resource-creator.jpg b/images/harmonyos-learning-resource-creator.jpg new file mode 100644 index 00000000..0d838b6c Binary files /dev/null and b/images/harmonyos-learning-resource-creator.jpg differ diff --git a/images/harmonyos-short-video-arkts.png b/images/harmonyos-short-video-arkts.png new file mode 100644 index 00000000..a9ab37a6 Binary files /dev/null and b/images/harmonyos-short-video-arkts.png differ diff --git a/images/huawei-developer-experts.png b/images/huawei-developer-experts.png new file mode 100644 index 00000000..a629a4e8 Binary files /dev/null and b/images/huawei-developer-experts.png differ diff --git a/images/huawei-developer-niuren-certificate-2022.jpg b/images/huawei-developer-niuren-certificate-2022.jpg new file mode 100644 index 00000000..76431abf Binary files /dev/null and b/images/huawei-developer-niuren-certificate-2022.jpg differ diff --git a/images/huawei-developer-niuren-certificate.png b/images/huawei-developer-niuren-certificate.png new file mode 100644 index 00000000..0dc31042 Binary files /dev/null and b/images/huawei-developer-niuren-certificate.png differ diff --git a/images/logo.jpg b/images/logo.jpg deleted file mode 100644 index 85179380..00000000 Binary files a/images/logo.jpg and /dev/null differ diff --git a/images/openharmony-certificate-2.jpg b/images/openharmony-certificate-2.jpg new file mode 100644 index 00000000..897fe92d Binary files /dev/null and b/images/openharmony-certificate-2.jpg differ diff --git a/images/openharmony-certificate.png b/images/openharmony-certificate.png new file mode 100644 index 00000000..943d377f Binary files /dev/null and b/images/openharmony-certificate.png differ diff --git a/images/tsinghua-university-press-2025-star-book-recommender.png b/images/tsinghua-university-press-2025-star-book-recommender.png new file mode 100644 index 00000000..8816b649 Binary files /dev/null and b/images/tsinghua-university-press-2025-star-book-recommender.png differ diff --git a/samples/AIScanner/.gitignore b/samples/AIScanner/.gitignore new file mode 100644 index 00000000..d2ff2014 --- /dev/null +++ b/samples/AIScanner/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/samples/AIScanner/AppScope/app.json5 b/samples/AIScanner/AppScope/app.json5 new file mode 100644 index 00000000..b285ab4d --- /dev/null +++ b/samples/AIScanner/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.waylau.hmos.aiscanner", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/samples/AIScanner/AppScope/resources/base/element/string.json b/samples/AIScanner/AppScope/resources/base/element/string.json new file mode 100644 index 00000000..da63d635 --- /dev/null +++ b/samples/AIScanner/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "AIScanner" + } + ] +} diff --git a/samples/AIScanner/AppScope/resources/base/media/background.png b/samples/AIScanner/AppScope/resources/base/media/background.png new file mode 100644 index 00000000..923f2b3f Binary files /dev/null and b/samples/AIScanner/AppScope/resources/base/media/background.png differ diff --git a/samples/AIScanner/AppScope/resources/base/media/foreground.png b/samples/AIScanner/AppScope/resources/base/media/foreground.png new file mode 100644 index 00000000..eb942758 Binary files /dev/null and b/samples/AIScanner/AppScope/resources/base/media/foreground.png differ diff --git a/samples/AIScanner/AppScope/resources/base/media/layered_image.json b/samples/AIScanner/AppScope/resources/base/media/layered_image.json new file mode 100644 index 00000000..fb499204 --- /dev/null +++ b/samples/AIScanner/AppScope/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/samples/AIScanner/build-profile.json5 b/samples/AIScanner/build-profile.json5 new file mode 100644 index 00000000..ee354812 --- /dev/null +++ b/samples/AIScanner/build-profile.json5 @@ -0,0 +1,56 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "C:\\Users\\wayla\\.ohos\\config\\default_AIScanner_Q0QHRVeb3QQyhu-thrRaeUDo99vz2sFnyS_Wg-pSoS0=.cer", + "keyAlias": "debugKey", + "keyPassword": "0000001B6DA69037926C7D89FB9E3C8ED182EFE48805B4B2289EC069A302989F99777B7DE4C980B10C4950", + "profile": "C:\\Users\\wayla\\.ohos\\config\\default_AIScanner_Q0QHRVeb3QQyhu-thrRaeUDo99vz2sFnyS_Wg-pSoS0=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\wayla\\.ohos\\config\\default_AIScanner_Q0QHRVeb3QQyhu-thrRaeUDo99vz2sFnyS_Wg-pSoS0=.p12", + "storePassword": "0000001B252C03F99D859F3FAB89415655C4780D2B0BD165EADCB698DEB21914ED0B7921356AB265B63309" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug", + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/AIScanner/code-linter.json5 b/samples/AIScanner/code-linter.json5 new file mode 100644 index 00000000..073990fa --- /dev/null +++ b/samples/AIScanner/code-linter.json5 @@ -0,0 +1,32 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/samples/AIScanner/entry/.gitignore b/samples/AIScanner/entry/.gitignore new file mode 100644 index 00000000..e2713a27 --- /dev/null +++ b/samples/AIScanner/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/samples/AIScanner/entry/build-profile.json5 b/samples/AIScanner/entry/build-profile.json5 new file mode 100644 index 00000000..6bd6457a --- /dev/null +++ b/samples/AIScanner/entry/build-profile.json5 @@ -0,0 +1,33 @@ +{ + "apiType": "stageMode", + "buildOption": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "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/samples/AIScanner/entry/hvigorfile.ts b/samples/AIScanner/entry/hvigorfile.ts new file mode 100644 index 00000000..b0e3a1ab --- /dev/null +++ b/samples/AIScanner/entry/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. */ +} \ No newline at end of file diff --git a/samples/AIScanner/entry/obfuscation-rules.txt b/samples/AIScanner/entry/obfuscation-rules.txt new file mode 100644 index 00000000..272efb6c --- /dev/null +++ b/samples/AIScanner/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/samples/AIScanner/entry/oh-package.json5 b/samples/AIScanner/entry/oh-package.json5 new file mode 100644 index 00000000..248c3b75 --- /dev/null +++ b/samples/AIScanner/entry/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": {} +} + diff --git a/samples/AIScanner/entry/src/main/ets/entryability/EntryAbility.ets b/samples/AIScanner/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 00000000..091797f5 --- /dev/null +++ b/samples/AIScanner/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,48 @@ +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/samples/AIScanner/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 00000000..8e4de992 --- /dev/null +++ b/samples/AIScanner/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,16 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/ets/pages/Index.ets b/samples/AIScanner/entry/src/main/ets/pages/Index.ets new file mode 100644 index 00000000..7854376e --- /dev/null +++ b/samples/AIScanner/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,66 @@ +@Entry +@Component +struct Index { + private dataSource: Array = + ['身份证识别', '银行卡识别', '文档扫描']; + @State searchContent: string = ''; + // 创建一个导航控制器对象并传入Navigation + pageInfos: NavPathStack = new NavPathStack(); + scroller: Scroller = new Scroller(); + layoutOptions: GridLayoutOptions = { + regularSize: [1, 1], + }; + + build() { + Navigation(this.pageInfos) { + Column() { + Column() { + Text('AI扫描') + .fontColor($r('sys.color.font_primary')) + .fontSize(24) + }.height('30%') + .width('100%') + .justifyContent(FlexAlign.Center) + .margin({ top: 10 }) + + Grid(this.scroller, this.layoutOptions) { + ForEach(this.dataSource, (item: string) => { + GridItem() { + Button(item) + .width('100%') + .height(48) + .fontSize(24) + .onClick(e => { + switch (item) { + case '身份证识别': + this.pageInfos.pushPathByName('PageIdCard', item); + break; + case '银行卡识别': + this.pageInfos.pushPathByName('PageBankCard', item); + break; + case '文档扫描': + this.pageInfos.pushPathByName('PageDocScan', item); + break; + } + }) + } + }, (item: string) => item + ) + } + .columnsGap(8) + .rowsGap(8) + .columnsTemplate('1fr 1fr') + .width('100%') + .height('100%') + + } + .width('100%') + .height('100%') + .margin({ bottom: 10 }) + + } + .mode(NavigationMode.Stack) + .hideTitleBar(true) + + } +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/ets/pages/PageBankCard.ets b/samples/AIScanner/entry/src/main/ets/pages/PageBankCard.ets new file mode 100644 index 00000000..cf99a410 --- /dev/null +++ b/samples/AIScanner/entry/src/main/ets/pages/PageBankCard.ets @@ -0,0 +1,100 @@ +import { CardRecognition, CardRecognitionResult, CardType, CardSide, ShootingMode } from "@kit.VisionKit" +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG: string = 'PageBankCard' + + +// 跳转页面入口函数 +@Builder +export function PageBankCardBuilder(name: string, param: string) { + PageBankCard(); +} + +@Entry +@Component +struct PageBankCard { + @State type: string = ''; + @State cardDataSource: Record[] = [] + pageInfos: NavPathStack = new NavPathStack(); + + build() { + NavDestination() { + Stack({ alignContent: Alignment.Top }) { + Stack() { + this.cardDataShowBuilder() + } + .width('80%') + .height('80%') + + CardRecognition({ + // 此处选择银行卡类型作为示例 + supportType: CardType.CARD_BANK, + // 银行卡为单面识别 + cardSide: CardSide.FRONT, + cardRecognitionConfig: { + defaultShootingMode: ShootingMode.MANUAL, + isPhotoSelectionSupported: true, + cardContentConfig: { bankCard: { isBankNumberDialogShown: true } } + }, + onResult: ((params: CardRecognitionResult) => { + hilog.info(0x0001, TAG, `params code: ${params.code}`) + if (params.code !== 200) { + this.pageInfos.pop() + } + hilog.info(0x0001, TAG, `params cardType: ${params.cardType}`) + if (params.cardInfo?.front !== undefined) { + this.cardDataSource.push(params.cardInfo?.front) + } + + if (params.cardInfo?.back !== undefined) { + this.cardDataSource.push(params.cardInfo?.back) + } + + if (params.cardInfo?.main !== undefined) { + this.cardDataSource.push(params.cardInfo?.main) + } + hilog.info(0x0001, TAG, `params cardInfo front: ${JSON.stringify(params.cardInfo?.front)}`) + hilog.info(0x0001, TAG, `params cardInfo back: ${JSON.stringify(params.cardInfo?.back)}`) + }) + }) + } + .width('100%') + .height('100%') + } + .title(this.type) + .onReady((ctx: NavDestinationContext) => { + // NavDestinationContext获取当前所在的导航控制器 + this.pageInfos = ctx.pathStack; + + // 获取参数 + this.type = ctx?.pathInfo?.param as string; + }) + } + + @Builder + cardDataShowBuilder() { + List() { + ForEach(this.cardDataSource, (cardData: Record) => { + ListItem() { + Column() { + Image(cardData.cardImageUri) + .objectFit(ImageFit.Contain) + .width(100) + .height(100) + + Text(JSON.stringify(cardData)) + .width('100%') + .fontSize(12) + } + } + }) + } + .listDirection(Axis.Vertical) + .alignListItem(ListItemAlign.Center) + .margin({ + top: 50 + }) + .width('100%') + .height('100%') + } +} diff --git a/samples/AIScanner/entry/src/main/ets/pages/PageDocScan.ets b/samples/AIScanner/entry/src/main/ets/pages/PageDocScan.ets new file mode 100644 index 00000000..c6f9a921 --- /dev/null +++ b/samples/AIScanner/entry/src/main/ets/pages/PageDocScan.ets @@ -0,0 +1,90 @@ +import { + DocType, + DocumentScanner, + DocumentScannerConfig, + SaveOption, + FilterId, + ShootingMode +} from "@kit.VisionKit" +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG: string = 'PageDocScan' + + +// 跳转页面入口函数 +@Builder +export function PageDocScanBuilder(name: string, param: string) { + PageDocScan(); +} + +@Entry +@Component +struct PageDocScan { + @State type: string = ''; + pageInfos: NavPathStack = new NavPathStack(); + + @State docImageUris: string[] = [] + private docScanConfig = new DocumentScannerConfig() + + aboutToAppear() { + this.docScanConfig.supportType = [DocType.DOC, DocType.SHEET] + this.docScanConfig.isGallerySupported = true + this.docScanConfig.editTabs = [] + this.docScanConfig.maxShotCount = 3 + this.docScanConfig.defaultFilterId = FilterId.ORIGINAL + this.docScanConfig.defaultShootingMode = ShootingMode.MANUAL + this.docScanConfig.isShareable = true + this.docScanConfig.originalUris = [] + } + + build() { + NavDestination() { + Stack({ alignContent: Alignment.Top }) { + //展示文档扫描结果 + List() { + ForEach(this.docImageUris, (uri: string) => { + ListItem() { + Image(uri) + .objectFit(ImageFit.Contain) + .width(100) + .height(100) + } + }) + } + .listDirection(Axis.Vertical) + .alignListItem(ListItemAlign.Center) + .margin({ + top: 50 + }) + .width('80%') + .height('80%') + + //文档扫描 + DocumentScanner({ + scannerConfig: this.docScanConfig, + onResult: (code: number, saveType: SaveOption, uris: string[]) => { + hilog.info(0x0001, TAG, `result code: ${code}, save: ${saveType}`) + if (code === -1) { + this.pageInfos.pop() + } + uris.forEach(uriString => { + hilog.info(0x0001, TAG, `uri: ${uriString}`) + }) + this.docImageUris = uris + } + }) + .size({ width: '100%', height: '100%' }) + } + .width('100%') + .height('100%') + } + .title(this.type) + .onReady((ctx: NavDestinationContext) => { + // NavDestinationContext获取当前所在的导航控制器 + this.pageInfos = ctx.pathStack; + + // 获取参数 + this.type = ctx?.pathInfo?.param as string; + }) + } +} diff --git a/samples/AIScanner/entry/src/main/ets/pages/PageIdCard.ets b/samples/AIScanner/entry/src/main/ets/pages/PageIdCard.ets new file mode 100644 index 00000000..07906b80 --- /dev/null +++ b/samples/AIScanner/entry/src/main/ets/pages/PageIdCard.ets @@ -0,0 +1,99 @@ +import { CardRecognition, CardRecognitionResult, CardType, CardSide, ShootingMode } from "@kit.VisionKit" +import { hilog } from '@kit.PerformanceAnalysisKit'; + +const TAG: string = 'PageIdCard' + + +// 跳转页面入口函数 +@Builder +export function PageIdCardBuilder(name: string, param: string) { + PageIdCard(); +} + +@Entry +@Component +struct PageIdCard { + @State type: string = ''; + @State cardDataSource: Record[] = [] + pageInfos: NavPathStack = new NavPathStack(); + + build() { + NavDestination() { + Stack({ alignContent: Alignment.Top }) { + Stack() { + this.cardDataShowBuilder() + } + .width('80%') + .height('80%') + + CardRecognition({ + // 此处选择身份证类型作为示例 + supportType: CardType.CARD_ID, + // 身份证为双面识别 + cardSide: CardSide.DEFAULT, + cardRecognitionConfig: { + defaultShootingMode: ShootingMode.MANUAL, + isPhotoSelectionSupported: true + }, + onResult: ((params: CardRecognitionResult) => { + hilog.info(0x0001, TAG, `params code: ${params.code}`) + if (params.code !== 200) { + this.pageInfos.pop() + } + hilog.info(0x0001, TAG, `params cardType: ${params.cardType}`) + if (params.cardInfo?.front !== undefined) { + this.cardDataSource.push(params.cardInfo?.front) + } + + if (params.cardInfo?.back !== undefined) { + this.cardDataSource.push(params.cardInfo?.back) + } + + if (params.cardInfo?.main !== undefined) { + this.cardDataSource.push(params.cardInfo?.main) + } + hilog.info(0x0001, TAG, `params cardInfo front: ${JSON.stringify(params.cardInfo?.front)}`) + hilog.info(0x0001, TAG, `params cardInfo back: ${JSON.stringify(params.cardInfo?.back)}`) + }) + }) + } + .width('100%') + .height('100%') + } + .title(this.type) + .onReady((ctx: NavDestinationContext) => { + // NavDestinationContext获取当前所在的导航控制器 + this.pageInfos = ctx.pathStack; + + // 获取参数 + this.type = ctx?.pathInfo?.param as string; + }) + } + + @Builder + cardDataShowBuilder() { + List() { + ForEach(this.cardDataSource, (cardData: Record) => { + ListItem() { + Column() { + Image(cardData.cardImageUri) + .objectFit(ImageFit.Contain) + .width(100) + .height(100) + + Text(JSON.stringify(cardData)) + .width('100%') + .fontSize(12) + } + } + }) + } + .listDirection(Axis.Vertical) + .alignListItem(ListItemAlign.Center) + .margin({ + top: 50 + }) + .width('100%') + .height('100%') + } +} diff --git a/samples/AIScanner/entry/src/main/module.json5 b/samples/AIScanner/entry/src/main/module.json5 new file mode 100644 index 00000000..d402f569 --- /dev/null +++ b/samples/AIScanner/entry/src/main/module.json5 @@ -0,0 +1,51 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ], + "routerMap": "$profile:route_map" + } +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/resources/base/element/color.json b/samples/AIScanner/entry/src/main/resources/base/element/color.json new file mode 100644 index 00000000..3c712962 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/resources/base/element/float.json b/samples/AIScanner/entry/src/main/resources/base/element/float.json new file mode 100644 index 00000000..33ea2230 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/samples/AIScanner/entry/src/main/resources/base/element/string.json b/samples/AIScanner/entry/src/main/resources/base/element/string.json new file mode 100644 index 00000000..f9459551 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/resources/base/media/background.png b/samples/AIScanner/entry/src/main/resources/base/media/background.png new file mode 100644 index 00000000..923f2b3f Binary files /dev/null and b/samples/AIScanner/entry/src/main/resources/base/media/background.png differ diff --git a/samples/AIScanner/entry/src/main/resources/base/media/foreground.png b/samples/AIScanner/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 00000000..97014d3e Binary files /dev/null and b/samples/AIScanner/entry/src/main/resources/base/media/foreground.png differ diff --git a/samples/AIScanner/entry/src/main/resources/base/media/layered_image.json b/samples/AIScanner/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 00000000..fb499204 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/resources/base/media/startIcon.png b/samples/AIScanner/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 00000000..205ad8b5 Binary files /dev/null and b/samples/AIScanner/entry/src/main/resources/base/media/startIcon.png differ diff --git a/samples/AIScanner/entry/src/main/resources/base/profile/backup_config.json b/samples/AIScanner/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 00000000..78f40ae7 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/resources/base/profile/main_pages.json b/samples/AIScanner/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 00000000..1898d94f --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/samples/AIScanner/entry/src/main/resources/base/profile/route_map.json b/samples/AIScanner/entry/src/main/resources/base/profile/route_map.json new file mode 100644 index 00000000..daee5303 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/base/profile/route_map.json @@ -0,0 +1,17 @@ +{ + "routerMap": [ + { + "name": "PageIdCard", + "pageSourceFile": "src/main/ets/pages/PageIdCard.ets", + "buildFunction": "PageIdCardBuilder" + },{ + "name": "PageBankCard", + "pageSourceFile": "src/main/ets/pages/PageBankCard.ets", + "buildFunction": "PageBankCardBuilder" + },{ + "name": "PageDocScan", + "pageSourceFile": "src/main/ets/pages/PageDocScan.ets", + "buildFunction": "PageDocScanBuilder" + } + ] +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/main/resources/dark/element/color.json b/samples/AIScanner/entry/src/main/resources/dark/element/color.json new file mode 100644 index 00000000..79b11c27 --- /dev/null +++ b/samples/AIScanner/entry/src/main/resources/dark/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#000000" + } + ] +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/mock/mock-config.json5 b/samples/AIScanner/entry/src/mock/mock-config.json5 new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/samples/AIScanner/entry/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/samples/AIScanner/entry/src/ohosTest/ets/test/Ability.test.ets b/samples/AIScanner/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 00000000..85c78f67 --- /dev/null +++ b/samples/AIScanner/entry/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/samples/AIScanner/entry/src/ohosTest/ets/test/List.test.ets b/samples/AIScanner/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 00000000..794c7dc4 --- /dev/null +++ b/samples/AIScanner/entry/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/samples/AIScanner/entry/src/ohosTest/module.json5 b/samples/AIScanner/entry/src/ohosTest/module.json5 new file mode 100644 index 00000000..509a3a28 --- /dev/null +++ b/samples/AIScanner/entry/src/ohosTest/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/samples/AIScanner/entry/src/test/List.test.ets b/samples/AIScanner/entry/src/test/List.test.ets new file mode 100644 index 00000000..bb5b5c37 --- /dev/null +++ b/samples/AIScanner/entry/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/samples/AIScanner/entry/src/test/LocalUnit.test.ets b/samples/AIScanner/entry/src/test/LocalUnit.test.ets new file mode 100644 index 00000000..165fc161 --- /dev/null +++ b/samples/AIScanner/entry/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/samples/AIScanner/hvigor/hvigor-config.json5 b/samples/AIScanner/hvigor/hvigor-config.json5 new file mode 100644 index 00000000..7a7ab891 --- /dev/null +++ b/samples/AIScanner/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | 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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/samples/AIScanner/hvigorfile.ts b/samples/AIScanner/hvigorfile.ts new file mode 100644 index 00000000..47113e2e --- /dev/null +++ b/samples/AIScanner/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. */ +} \ No newline at end of file diff --git a/samples/AIScanner/oh-package-lock.json5 b/samples/AIScanner/oh-package-lock.json5 new file mode 100644 index 00000000..6b264af2 --- /dev/null +++ b/samples/AIScanner/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.24": "@ohos/hypium@1.0.24" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.24": { + "name": "", + "version": "1.0.24", + "integrity": "sha512-3dCqc+BAR5LqEGG2Vtzi8O3r7ci/3fYU+FWjwvUobbfko7DUnXGOccaror0yYuUhJfXzFK0aZNMGSnXaTwEnbw==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.24.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/samples/AIScanner/oh-package.json5 b/samples/AIScanner/oh-package.json5 new file mode 100644 index 00000000..c72aa05d --- /dev/null +++ b/samples/AIScanner/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/samples/AVMetadataHelper/.gitignore b/samples/AVMetadataHelper/.gitignore index 0f1d1370..13e3ccdb 100644 --- a/samples/AVMetadataHelper/.gitignore +++ b/samples/AVMetadataHelper/.gitignore @@ -6,4 +6,5 @@ /build /captures .externalNativeBuild +/entry/.preview .cxx diff --git a/samples/AVMetadataHelper/build.gradle b/samples/AVMetadataHelper/build.gradle index f1929163..ffb7d0b2 100644 --- a/samples/AVMetadataHelper/build.gradle +++ b/samples/AVMetadataHelper/build.gradle @@ -1,36 +1,36 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. apply plugin: 'com.huawei.ohos.app' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#section1112183053510 ohos { - compileSdkVersion 4 + compileSdkVersion 5 defaultConfig { - compatibleSdkVersion 4 + compatibleSdkVersion 5 } } buildscript { repositories { maven { - url 'https://mirrors.huaweicloud.com/repository/maven/' + url 'https://repo.huaweicloud.com/repository/maven/' } maven { url 'https://developer.huawei.com/repo/' } - jcenter() } dependencies { - classpath 'com.huawei.ohos:hap:2.4.1.4' + classpath 'com.huawei.ohos:hap:2.4.5.0' + classpath 'com.huawei.ohos:decctest:1.2.4.1' } } allprojects { repositories { maven { - url 'https://mirrors.huaweicloud.com/repository/maven/' + url 'https://repo.huaweicloud.com/repository/maven/' } maven { url 'https://developer.huawei.com/repo/' } - jcenter() } } diff --git a/samples/AVMetadataHelper/entry/build.gradle b/samples/AVMetadataHelper/entry/build.gradle index 27e6f932..8b7ec831 100644 --- a/samples/AVMetadataHelper/entry/build.gradle +++ b/samples/AVMetadataHelper/entry/build.gradle @@ -1,13 +1,27 @@ apply plugin: 'com.huawei.ohos.hap' +apply plugin: 'com.huawei.ohos.decctest' +//For instructions on signature configuration, see https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ide_debug_device-0000001053822404#section1112183053510 ohos { - compileSdkVersion 4 + compileSdkVersion 5 defaultConfig { - compatibleSdkVersion 4 + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) - testCompile 'junit:junit:4.12' + testImplementation 'junit:junit:4.13' + ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.200' +} +decc { + supportType = ['html','xml'] } diff --git a/samples/AVMetadataHelper/entry/proguard-rules.pro b/samples/AVMetadataHelper/entry/proguard-rules.pro new file mode 100644 index 00000000..f7666e47 --- /dev/null +++ b/samples/AVMetadataHelper/entry/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/samples/AVMetadataHelper/entry/src/main/config.json b/samples/AVMetadataHelper/entry/src/main/config.json index 6e3e6a57..aaed2f61 100644 --- a/samples/AVMetadataHelper/entry/src/main/config.json +++ b/samples/AVMetadataHelper/entry/src/main/config.json @@ -3,26 +3,24 @@ "bundleName": "com.waylau.hmos.avmetadatahelper", "vendor": "waylau", "version": { - "code": 1, - "name": "1.0" - }, - "apiVersion": { - "compatible": 4, - "target": 4, - "releaseType": "Beta1" + "code": 1000000, + "name": "1.0.0" } }, "deviceConfig": {}, "module": { "package": "com.waylau.hmos.avmetadatahelper", "name": ".MyApplication", + "mainAbility": "com.waylau.hmos.avmetadatahelper.MainAbility", "deviceType": [ + "phone", "tv" ], "distro": { "deliveryWithInstall": true, "moduleName": "entry", - "moduleType": "entry" + "moduleType": "entry", + "installationFree": false }, "abilities": [ { @@ -36,11 +34,11 @@ ] } ], - "orientation": "landscape", + "orientation": "unspecified", "name": "com.waylau.hmos.avmetadatahelper.MainAbility", "icon": "$media:icon", "description": "$string:mainability_description", - "label": "AVMetadataHelper", + "label": "$string:entry_MainAbility", "type": "page", "launchType": "standard" } diff --git a/samples/AVMetadataHelper/entry/src/main/resources/base/element/string.json b/samples/AVMetadataHelper/entry/src/main/resources/base/element/string.json index 49d8b1d5..c2a6b66c 100644 --- a/samples/AVMetadataHelper/entry/src/main/resources/base/element/string.json +++ b/samples/AVMetadataHelper/entry/src/main/resources/base/element/string.json @@ -1,15 +1,15 @@ { "string": [ { - "name": "app_name", - "value": "AVMetadataHelper" + "name": "entry_MainAbility", + "value": "entry_MainAbility" }, { "name": "mainability_description", - "value": "Java_TV_Empty Feature Ability" + "value": "Java_Empty Ability" }, { - "name": "HelloWorld", + "name": "mainability_HelloWorld", "value": "Hello World" } ] diff --git a/samples/AVMetadataHelper/entry/src/main/resources/base/layout/ability_main.xml b/samples/AVMetadataHelper/entry/src/main/resources/base/layout/ability_main.xml index a9b6d8ea..f8d78228 100644 --- a/samples/AVMetadataHelper/entry/src/main/resources/base/layout/ability_main.xml +++ b/samples/AVMetadataHelper/entry/src/main/resources/base/layout/ability_main.xml @@ -7,18 +7,19 @@ + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/hm_sample_view_video_box_seek_bar_style1.xml b/samples/Douyin/entry/src/main/resources/base/layout/hm_sample_view_video_box_seek_bar_style1.xml new file mode 100644 index 00000000..ef83e0fa --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/hm_sample_view_video_box_seek_bar_style1.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/hm_sample_view_video_box_seek_bar_style2.xml b/samples/Douyin/entry/src/main/resources/base/layout/hm_sample_view_video_box_seek_bar_style2.xml new file mode 100644 index 00000000..4ec84ffa --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/hm_sample_view_video_box_seek_bar_style2.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_control.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_control.xml new file mode 100644 index 00000000..ae24c68b --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_control.xml @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_episodes.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_episodes.xml new file mode 100644 index 00000000..391c2645 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_episodes.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_select_devices.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_select_devices.xml new file mode 100644 index 00000000..798c4e08 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_select_devices.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_sound_equipment.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_sound_equipment.xml new file mode 100644 index 00000000..775dab9d --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_ability_sound_equipment.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_device_item.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_device_item.xml new file mode 100644 index 00000000..f7ebae73 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_device_item.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_episodes_item.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_episodes_item.xml new file mode 100644 index 00000000..24060412 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_episodes_item.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/layout/remote_video_quality_item.xml b/samples/Douyin/entry/src/main/resources/base/layout/remote_video_quality_item.xml new file mode 100644 index 00000000..81d4d375 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/base/layout/remote_video_quality_item.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_anthology.png b/samples/Douyin/entry/src/main/resources/base/media/ic_anthology.png new file mode 100644 index 00000000..32744db6 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_anthology.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_back.png b/samples/Douyin/entry/src/main/resources/base/media/ic_back.png new file mode 100644 index 00000000..22e9f34d Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_back.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_barrage.png b/samples/Douyin/entry/src/main/resources/base/media/ic_barrage.png new file mode 100644 index 00000000..4eb44cf0 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_barrage.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_circular_pause.png b/samples/Douyin/entry/src/main/resources/base/media/ic_circular_pause.png new file mode 100644 index 00000000..b33f562d Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_circular_pause.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_circular_play.png b/samples/Douyin/entry/src/main/resources/base/media/ic_circular_play.png new file mode 100644 index 00000000..949cc735 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_circular_play.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_clipping.png b/samples/Douyin/entry/src/main/resources/base/media/ic_clipping.png new file mode 100644 index 00000000..4f8ffc55 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_clipping.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_close_panel.png b/samples/Douyin/entry/src/main/resources/base/media/ic_close_panel.png new file mode 100644 index 00000000..049fd0f5 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_close_panel.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_device_matebook.png b/samples/Douyin/entry/src/main/resources/base/media/ic_device_matebook.png new file mode 100644 index 00000000..8be6cf91 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_device_matebook.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_device_phone.png b/samples/Douyin/entry/src/main/resources/base/media/ic_device_phone.png new file mode 100644 index 00000000..11347e1d Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_device_phone.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_down_arrow.png b/samples/Douyin/entry/src/main/resources/base/media/ic_down_arrow.png new file mode 100644 index 00000000..702540fa Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_down_arrow.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_gif.png b/samples/Douyin/entry/src/main/resources/base/media/ic_gif.png new file mode 100644 index 00000000..10d1d96f Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_gif.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_locked.png b/samples/Douyin/entry/src/main/resources/base/media/ic_locked.png new file mode 100644 index 00000000..69cfdba5 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_locked.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_mute.png b/samples/Douyin/entry/src/main/resources/base/media/ic_mute.png new file mode 100644 index 00000000..0115c2f9 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_mute.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_next_anthology.png b/samples/Douyin/entry/src/main/resources/base/media/ic_next_anthology.png new file mode 100644 index 00000000..bc44ef6d Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_next_anthology.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_orientation_switchover.png b/samples/Douyin/entry/src/main/resources/base/media/ic_orientation_switchover.png new file mode 100644 index 00000000..3dc60cbb Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_orientation_switchover.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_pad.png b/samples/Douyin/entry/src/main/resources/base/media/ic_pad.png new file mode 100644 index 00000000..6620e339 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_pad.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_pause.png b/samples/Douyin/entry/src/main/resources/base/media/ic_pause.png new file mode 100644 index 00000000..14ac43d2 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_pause.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_pause_black.png b/samples/Douyin/entry/src/main/resources/base/media/ic_pause_black.png new file mode 100644 index 00000000..a0d6fef6 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_pause_black.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_play.png b/samples/Douyin/entry/src/main/resources/base/media/ic_play.png new file mode 100644 index 00000000..0538b982 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_play.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_play_black.png b/samples/Douyin/entry/src/main/resources/base/media/ic_play_black.png new file mode 100644 index 00000000..55b302e0 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_play_black.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_playing.png b/samples/Douyin/entry/src/main/resources/base/media/ic_playing.png new file mode 100644 index 00000000..2ce7c022 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_playing.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_pop_dialog.png b/samples/Douyin/entry/src/main/resources/base/media/ic_pop_dialog.png new file mode 100644 index 00000000..39472a6e Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_pop_dialog.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_radio_empty.png b/samples/Douyin/entry/src/main/resources/base/media/ic_radio_empty.png new file mode 100644 index 00000000..84bdde51 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_radio_empty.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_recording.png b/samples/Douyin/entry/src/main/resources/base/media/ic_recording.png new file mode 100644 index 00000000..845dadd1 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_recording.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_reject_message.png b/samples/Douyin/entry/src/main/resources/base/media/ic_reject_message.png new file mode 100644 index 00000000..e14b4c8d Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_reject_message.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_right_arrow.png b/samples/Douyin/entry/src/main/resources/base/media/ic_right_arrow.png new file mode 100644 index 00000000..e2c9ee13 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_right_arrow.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_selected_radio.png b/samples/Douyin/entry/src/main/resources/base/media/ic_selected_radio.png new file mode 100644 index 00000000..0f9c52ff Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_selected_radio.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_share.png b/samples/Douyin/entry/src/main/resources/base/media/ic_share.png new file mode 100644 index 00000000..797f2c42 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_share.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_sound.png b/samples/Douyin/entry/src/main/resources/base/media/ic_sound.png new file mode 100644 index 00000000..19948850 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_sound.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_soundx.png b/samples/Douyin/entry/src/main/resources/base/media/ic_soundx.png new file mode 100644 index 00000000..2e556472 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_soundx.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_speed1_5.png b/samples/Douyin/entry/src/main/resources/base/media/ic_speed1_5.png new file mode 100644 index 00000000..f82c10d9 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_speed1_5.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_timing.png b/samples/Douyin/entry/src/main/resources/base/media/ic_timing.png new file mode 100644 index 00000000..38eb7420 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_timing.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_toggle_tv_playback.png b/samples/Douyin/entry/src/main/resources/base/media/ic_toggle_tv_playback.png new file mode 100644 index 00000000..75559283 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_toggle_tv_playback.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_toggle_window_playback.png b/samples/Douyin/entry/src/main/resources/base/media/ic_toggle_window_playback.png new file mode 100644 index 00000000..2488626f Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_toggle_window_playback.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_trailer.png b/samples/Douyin/entry/src/main/resources/base/media/ic_trailer.png new file mode 100644 index 00000000..861f1cb2 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_trailer.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_transfer.png b/samples/Douyin/entry/src/main/resources/base/media/ic_transfer.png new file mode 100644 index 00000000..b46fa97d Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_transfer.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_tv.png b/samples/Douyin/entry/src/main/resources/base/media/ic_tv.png new file mode 100644 index 00000000..21eae3fc Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_tv.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_video_back.png b/samples/Douyin/entry/src/main/resources/base/media/ic_video_back.png new file mode 100644 index 00000000..ed1a5976 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_video_back.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_voice.png b/samples/Douyin/entry/src/main/resources/base/media/ic_voice.png new file mode 100644 index 00000000..4de2519b Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_voice.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_watch.png b/samples/Douyin/entry/src/main/resources/base/media/ic_watch.png new file mode 100644 index 00000000..2e942a92 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_watch.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/ic_white_down_arrow.png b/samples/Douyin/entry/src/main/resources/base/media/ic_white_down_arrow.png new file mode 100644 index 00000000..b10868a0 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/ic_white_down_arrow.png differ diff --git a/samples/Douyin/entry/src/main/resources/base/media/icon.png b/samples/Douyin/entry/src/main/resources/base/media/icon.png new file mode 100644 index 00000000..ce307a88 Binary files /dev/null and b/samples/Douyin/entry/src/main/resources/base/media/icon.png differ diff --git a/samples/Douyin/entry/src/main/resources/en_US/element/string.json b/samples/Douyin/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 00000000..e3565d10 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,116 @@ +{ + "string": [ + { + "name": "local_machine", + "value": "Local Machine" + }, + { + "name": "my_devices", + "value": "My Devices" + }, + { + "name": "sound_channel", + "value": "Sound Channel" + }, + { + "name": "control_play", + "value": "OK" + }, + { + "name": "control_playing_episodes", + "value": "Episode ?" + }, + { + "name": "control_episodes", + "value": "Episodes" + }, + { + "name": "control_all_episodes", + "value": "? episodes" + }, + { + "name": "control_episodes_auto", + "value": "AUTO" + }, + { + "name": "control_episodes_standard_quality", + "value": "SD 480P" + }, + { + "name": "control_episodes_high_quality", + "value": "HD 720P" + }, + { + "name": "control_episodes_full_high_quality", + "value": "FHD 1080P" + }, + { + "name": "control_episodes_hdr", + "value": "HDR" + }, + { + "name": "short_name_control_episodes_auto", + "value": "AUTO" + }, + { + "name": "short_name_control_episodes_standard_quality", + "value": "SD" + }, + { + "name": "short_name_control_episodes_high_quality", + "value": "HD" + }, + { + "name": "short_name_control_episodes_full_high_quality", + "value": "FHD" + }, + { + "name": "short_name_control_episodes_hdr", + "value": "HDR" + }, + { + "name": "control_equipment_select", + "value": "Sound-Producing Equipment" + }, + { + "name": "control_equipment_select_cancle", + "value": "Cancel" + }, + { + "name": "control_trailer", + "value": "trailer" + }, + { + "name": "playback_speed", + "value": "Playback Speed" + }, + { + "name": "select_playlist", + "value": "Episodes" + }, + { + "name": "select_resolution", + "value": "HD" + }, + { + "name": "send_failed_tips", + "value": "Failed to send the message." + }, + { + "name": "progress_start_time", + "value": "00:00" + }, + { + "name": "media_file_loading_error", + "value": "Media file loading error" + }, + { + "name": "invalid_operation", + "value": "Invalid_operation" + }, + { + "name": "undefined_error_type", + "value": "Undefined error type" + } + ] +} \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/rawfile/videos.json b/samples/Douyin/entry/src/main/resources/rawfile/videos.json new file mode 100644 index 00000000..e3bf54f4 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/rawfile/videos.json @@ -0,0 +1,83 @@ +{ + "name": "Huawei Consumer News", + "total_anthology": 7, + "anthology_detail": [ + { + "current_anthology": 1, + "autoUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "plot": "Leap further ahead with HUAWEI Mate 40", + "totalTime": "1:47", + "isTrailer": false + }, + { + "current_anthology": 2, + "autoUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "plot": "Imagine What Unfolds: Huawei Announces HUAWEI Mate X2", + "totalTime": "00:47", + "isTrailer": false + }, + { + "current_anthology": 3, + "autoUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "plot": "HUAWEI unveil porsche design HUAWEI WATCH GT2", + "totalTime": "00:41", + "isTrailer": false + }, + { + "current_anthology": 4, + "autoUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "plot": "Leap further ahead with HUAWEI Mate 40", + "totalTime": "1:47", + "isTrailer": false + }, + { + "current_anthology": 5, + "autoUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv2.mp4", + "plot": "Imagine What Unfolds: Huawei Announces HUAWEI Mate X2", + "totalTime": "00:47", + "isTrailer": false + }, + { + "current_anthology": 6, + "autoUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv3.mp4", + "plot": "HUAWEI unveil porsche design HUAWEI WATCH GT2", + "totalTime": "00:41", + "isTrailer": false + }, + { + "current_anthology": 7, + "autoUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "sdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "hdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "fhdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "ultraHdUrl": "entry/resources/base/media/hm_sample_pv.mp4", + "plot": "Leap further ahead with HUAWEI Mate 40", + "totalTime": "1:47", + "isTrailer": false + } + ] +} \ No newline at end of file diff --git a/samples/Douyin/entry/src/main/resources/zh_CN/element/string.json b/samples/Douyin/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 00000000..83c69c70 --- /dev/null +++ b/samples/Douyin/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,116 @@ +{ + "string": [ + { + "name": "local_machine", + "value": "本机" + }, + { + "name": "my_devices", + "value": "我的设备" + }, + { + "name": "sound_channel", + "value": "关密" + }, + { + "name": "control_play", + "value": "OK" + }, + { + "name": "control_playing_episodes", + "value": "第?集" + }, + { + "name": "control_episodes", + "value": "剧集" + }, + { + "name": "control_all_episodes", + "value": "?集全" + }, + { + "name": "control_episodes_auto", + "value": "自动" + }, + { + "name": "control_episodes_standard_quality", + "value": "标清480P" + }, + { + "name": "control_episodes_high_quality", + "value": "高清720P" + }, + { + "name": "control_episodes_full_high_quality", + "value": "蓝光1080P" + }, + { + "name": "control_episodes_hdr", + "value": "HDR幻彩视界" + }, + { + "name": "short_name_control_episodes_auto", + "value": "自动" + }, + { + "name": "short_name_control_episodes_standard_quality", + "value": "标清" + }, + { + "name": "short_name_control_episodes_high_quality", + "value": "高清" + }, + { + "name": "short_name_control_episodes_full_high_quality", + "value": "蓝光" + }, + { + "name": "short_name_control_episodes_hdr", + "value": "HDR" + }, + { + "name": "control_equipment_select", + "value": "出声设备" + }, + { + "name": "control_equipment_select_cancle", + "value": "取消" + }, + { + "name": "control_trailer", + "value": "预告" + }, + { + "name": "playback_speed", + "value": "倍速" + }, + { + "name": "select_playlist", + "value": "选集" + }, + { + "name": "select_resolution", + "value": "高清" + }, + { + "name": "send_failed_tips", + "value": "消息发送失败" + }, + { + "name": "progress_start_time", + "value": "00:00" + }, + { + "name": "media_file_loading_error", + "value": "视频文件加载失败" + }, + { + "name": "invalid_operation", + "value": "无效操作" + }, + { + "name": "undefined_error_type", + "value": "未知错误" + } + ] +} \ No newline at end of file diff --git a/samples/Douyin/entry/src/ohosTest/java/com/waylau/hmos/douyin/ExampleOhosTest.java b/samples/Douyin/entry/src/ohosTest/java/com/waylau/hmos/douyin/ExampleOhosTest.java new file mode 100644 index 00000000..1d583594 --- /dev/null +++ b/samples/Douyin/entry/src/ohosTest/java/com/waylau/hmos/douyin/ExampleOhosTest.java @@ -0,0 +1,14 @@ +package com.waylau.hmos.douyin; + +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExampleOhosTest { + @Test + public void testBundleName() { + final String actualBundleName = AbilityDelegatorRegistry.getArguments().getTestBundleName(); + assertEquals("com.waylau.hmos.douyin", actualBundleName); + } +} \ No newline at end of file diff --git a/samples/Douyin/entry/src/test/java/com/waylau/hmos/douyin/ExampleTest.java b/samples/Douyin/entry/src/test/java/com/waylau/hmos/douyin/ExampleTest.java new file mode 100644 index 00000000..69e6e75c --- /dev/null +++ b/samples/Douyin/entry/src/test/java/com/waylau/hmos/douyin/ExampleTest.java @@ -0,0 +1,9 @@ +package com.waylau.hmos.douyin; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/samples/Douyin/entrytv/.gitignore b/samples/Douyin/entrytv/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/samples/Douyin/entrytv/.gitignore @@ -0,0 +1 @@ +/build diff --git a/samples/Douyin/entrytv/build.gradle b/samples/Douyin/entrytv/build.gradle new file mode 100644 index 00000000..a0612a25 --- /dev/null +++ b/samples/Douyin/entrytv/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.huawei.ohos.hap' +apply plugin: 'com.huawei.ohos.decctest' +ohos { + compileSdkVersion 5 + defaultConfig { + compatibleSdkVersion 5 + } + buildTypes { + release { + proguardOpt { + proguardEnabled false + rulesFiles 'proguard-rules.pro' + } + } + } + entryModules "entry" +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar', '*.har']) + testImplementation 'junit:junit:4.13' + ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.200' + implementation project(path: ':commonlib') +} +decc { + supportType = ['html','xml'] +} \ No newline at end of file diff --git a/samples/Douyin/entrytv/package.json b/samples/Douyin/entrytv/package.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/samples/Douyin/entrytv/package.json @@ -0,0 +1 @@ +{} diff --git a/samples/Douyin/entrytv/proguard-rules.pro b/samples/Douyin/entrytv/proguard-rules.pro new file mode 100644 index 00000000..f7666e47 --- /dev/null +++ b/samples/Douyin/entrytv/proguard-rules.pro @@ -0,0 +1 @@ +# config module specific ProGuard rules here. \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/config.json b/samples/Douyin/entrytv/src/main/config.json new file mode 100644 index 00000000..d3f4f2fa --- /dev/null +++ b/samples/Douyin/entrytv/src/main/config.json @@ -0,0 +1,116 @@ +{ + "app": { + "bundleName": "com.waylau.hmos.douyin", + "vendor": "waylau", + "version": { + "code": 1000000, + "name": "1.0.0" + }, + "type": "normal" + }, + "deviceConfig": {}, + "module": { + "package": "com.waylau.hmos.douyin", + "name": ".MyApplication", + "mainAbility": "com.waylau.hmos.douyin.MainAbility", + "reqPermissions": [ + { + "name": "ohos.permission.INTERNET", + "reason": "", + "usedScene": { + "ability": [ + "VideoPlayAbilitySlice" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.DISTRIBUTED_DATASYNC", + "reason": "", + "usedScene": { + "ability": [ + "VideoPlayAbilitySlice" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE", + "reason": "", + "usedScene": { + "ability": [ + "VideoPlayAbilitySlice" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO", + "reason": "", + "usedScene": { + "ability": [ + "VideoPlayAbilitySlice" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.GET_BUNDLE_INFO", + "reason": "", + "usedScene": { + "ability": [ + "VideoPlayAbilitySlice" + ], + "when": "inuse" + } + }, + { + "name": "ohos.permission.KEEP_BACKGROUND_RUNNING", + "reason": "", + "usedScene": { + "ability": [ + "VideoPlayAbilitySlice" + ], + "when": "inuse" + } + } + ], + "deviceType": [ + "tv" + ], + "distro": { + "deliveryWithInstall": true, + "moduleName": "entrytv", + "moduleType": "entry", + "installationFree": false + }, + "abilities": [ + { + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home", + "action.video.play" + ] + } + ], + "orientation": "landscape", + "visible": true, + "name": "com.waylau.hmos.douyin.MainAbility", + "icon": "$media:icon", + "description": "$string:mainability_description", + "label": "$string:entrytv_MainAbility", + "type": "page", + "launchType": "standard" + }, + { + "visible": true, + "name": "com.waylau.hmos.douyin.VideoControlServiceAbility", + "type": "service" + } + ] + } +} \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/MainAbility.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/MainAbility.java new file mode 100644 index 00000000..7216fff2 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/MainAbility.java @@ -0,0 +1,20 @@ +package com.waylau.hmos.douyin; + +import com.waylau.hmos.douyin.slice.VideoPlayAbilitySlice; + +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; + +/** + * MainAbility + */ +public class MainAbility extends Ability { + @Override + public void onStart(Intent intent) { + requestPermissionsFromUser(new String[]{"ohos.permission.DISTRIBUTED_DATASYNC"}, 0); + + super.onStart(intent); + super.setMainRoute(VideoPlayAbilitySlice.class.getName()); + super.addActionRoute("action.video.play", VideoPlayAbilitySlice.class.getName()); + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/MyApplication.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/MyApplication.java new file mode 100644 index 00000000..7888def3 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/MyApplication.java @@ -0,0 +1,13 @@ +package com.waylau.hmos.douyin; + +import ohos.aafwk.ability.AbilityPackage; + +/** + * MyApplication + */ +public class MyApplication extends AbilityPackage { + @Override + public void onInitialize() { + super.onInitialize(); + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/VideoControlServiceAbility.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/VideoControlServiceAbility.java new file mode 100644 index 00000000..2b43a844 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/VideoControlServiceAbility.java @@ -0,0 +1,53 @@ +package com.waylau.hmos.douyin; + +import com.waylau.hmos.douyin.constant.RemoteConstant; +import com.waylau.hmos.douyin.constant.Constants; +import com.waylau.hmos.douyin.remote.MyRemote; + +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; +import ohos.aafwk.content.Operation; +import ohos.event.commonevent.CommonEventData; +import ohos.event.commonevent.CommonEventManager; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.rpc.IRemoteObject; +import ohos.rpc.RemoteException; + +import java.util.Map; + +/** + * Video Remote Control Service + */ +public class VideoControlServiceAbility extends Ability { + private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "VideoControlServiceAbility"); + private final MyRemote remote = new MyRemote(RemoteConstant.REQUEST_CONTROL_REMOTE_DEVICE); + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + remote.setRemoteRequestCallback(this::sendEvent); + } + + @Override + protected IRemoteObject onConnect(Intent intent) { + super.onConnect(intent); + return remote.asObject(); + } + + private void sendEvent(int controlCode, Map value) { + try { + Intent intent = new Intent(); + Operation operation = new Intent.OperationBuilder() + .withAction(Constants.TV_CONTROL_EVENT) + .build(); + intent.setOperation(operation); + intent.setParam(Constants.KEY_CONTROL_CODE, controlCode); + intent.setParam(Constants.KEY_CONTROL_VALUE, (String) value.get(RemoteConstant.REMOTE_KEY_CONTROL_VALUE)); + CommonEventData eventData = new CommonEventData(intent); + CommonEventManager.publishCommonEvent(eventData); + } catch (RemoteException e) { + HiLog.error(LABEL, "publishCommonEvent occur exception."); + } + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/component/VideoSetting.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/component/VideoSetting.java new file mode 100644 index 00000000..ea6820e5 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/component/VideoSetting.java @@ -0,0 +1,365 @@ +package com.waylau.hmos.douyin.component; + +import com.waylau.hmos.douyin.ResourceTable; +import com.waylau.hmos.douyin.constant.Constants; +import com.waylau.hmos.douyin.constant.ResolutionEnum; +import com.waylau.hmos.douyin.constant.SettingOptionEnum; +import com.waylau.hmos.douyin.constant.SpeedEnum; +import com.waylau.hmos.douyin.data.VideoInfo; +import com.waylau.hmos.douyin.data.VideoInfoService; +import com.waylau.hmos.douyin.data.Videos; +import com.waylau.hmos.douyin.model.SettingComponentTag; +import com.waylau.hmos.douyin.model.SettingModel; +import com.waylau.hmos.douyin.model.VideoModel; +import com.waylau.hmos.douyin.provider.SettingProvider; +import com.waylau.hmos.douyin.provider.VideoEpisodesSelectProvider; +import com.waylau.hmos.douyin.provider.VideoSettingProvider; +import com.waylau.hmos.douyin.utils.AppUtil; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.AttrHelper; +import ohos.agp.components.Component; +import ohos.agp.components.DirectionalLayout; +import ohos.agp.components.LayoutScatter; +import ohos.agp.components.ListContainer; +import ohos.agp.components.Text; +import ohos.multimodalinput.event.KeyEvent; + +import java.util.ArrayList; +import java.util.List; + +/** + * Video Settings Page + */ +public class VideoSetting extends DirectionalLayout { + private final AbilitySlice slice; + // Video information acquisition service, which can implement its own services + private final VideoInfoService videoInfoService; + private Component settingView; + private boolean isShow; + private VideoSettingCallback videoSettingCallback; + private int lastPos = Constants.INVALID_POSITION; + private int currentPlayingIndex = 0; + + // Remote control keys listen to events. + // Only the upper-level and lower-level confirmation keys need to be processed. + private final Component.KeyEventListener itemOnKeyListener = + ((component, keyEvent) -> { + if (!keyEvent.isKeyDown()) { + return false; + } + ListContainer settingContainer = + (ListContainer) this.findComponentById(ResourceTable.Id_video_setting_container); + SettingComponentTag tag = null; + if (component.getTag() instanceof SettingComponentTag) { + tag = (SettingComponentTag) component.getTag(); + } + switch (keyEvent.getKeyCode()) { + case KeyEvent.KEY_ENTER: + case KeyEvent.KEY_DPAD_CENTER: + if (tag == null + || videoSettingCallback == null + || !tag.getLever().equals(Constants.SETTING_OPTION_LEVER2)) { + break; + } + ListContainer child = + (ListContainer) + settingContainer + .getComponentAt(tag.getParentType()) + .findComponentById(ResourceTable.Id_video_setting_option_content); + if (tag.getParentType() == SettingOptionEnum.SPEED_SELECTION.ordinal()) { + float speed = SpeedEnum.values()[child.getIndexForComponent(component)].getValue(); + videoSettingCallback.setVideoSpeed(speed); + return true; + } + if (tag.getParentType() == SettingOptionEnum.RESOLUTION.ordinal()) { + String url = getResolutionUrl(child, component); + if (!"".equals(url)) { + videoSettingCallback.refreshVideoPath(url, ""); + } + return true; + } + if (tag.getParentType() == SettingOptionEnum.EPISODES.ordinal()) { + VideoInfo videoInfo = getEpisodesInfo(child, component); + if (videoInfo != null) { + videoSettingCallback.refreshVideoPath(videoInfo.getResolutions().get(0).getUrl(), + videoInfo.getVideoDesc()); + } + return true; + } + break; + case KeyEvent.KEY_DPAD_DOWN: + case KeyEvent.KEY_DPAD_UP: + if (tag != null && tag.getLever().equals(Constants.SETTING_OPTION_LEVER2)) { + // When the focus is on a subcomponent, press the up and down arrow keys + // to display the focus to the corresponding parent component. + int nextIndex = + keyEvent.getKeyCode() == KeyEvent.KEY_DPAD_DOWN + ? tag.getParentType() + 1 + : tag.getParentType() - 1; + if (nextIndex >= 0 && nextIndex < settingContainer.getChildCount()) { + settingContainer.getComponentAt(nextIndex).requestFocus(); + } + } + return true; + default: + break; + } + return false; + }); + + public VideoSetting(AbilitySlice slice, VideoInfoService videoInfoService) { + super(slice); + this.slice = slice; + this.videoInfoService = videoInfoService; + + if (settingView == null) { + settingView = + LayoutScatter.getInstance(slice).parse(ResourceTable.Layout_video_setting, null, false); + } + addComponent(settingView); + setVisibility(INVISIBLE); + initI18NResource(); + } + + /** + * Control the display of the setting page. + */ + public void show() { + if (isShow) { + return; + } + lastPos = Constants.INVALID_POSITION; + isShow = true; + initView(); + setVisibility(VISIBLE); + } + + /** + * Control settings page hide. + */ + public void hide() { + if (!isShow) { + return; + } + isShow = false; + clearView(); + setVisibility(INVISIBLE); + } + + /** + * Obtains the display status of the control page. + * + * @return isShow isShow + */ + public boolean isShow() { + return isShow; + } + + /** + * Register the callback event. + * + * @param videoSettingCallback callback event + */ + public void registerVideoSettingCallback(VideoSettingCallback videoSettingCallback) { + this.videoSettingCallback = videoSettingCallback; + } + + private void initView() { + ListContainer settingContainer = + (ListContainer) settingView.findComponentById(ResourceTable.Id_video_setting_container); + setSettingList(settingContainer); + settingContainer.setItemClickedListener( + (container, component, position, id) -> { + for (int i = 0; i < 3; i++) { + if (i != position) { + Text lastOptionName = + (Text) + container + .getComponentAt(i) + .findComponentById(ResourceTable.Id_video_setting_option_name); + lastOptionName.setAlpha(0.4f); + lastOptionName.setTextSize(AttrHelper.fp2px(18, slice)); + } + } + if (lastPos == position) { + return; + } + // Style unselected options + if (lastPos != Constants.INVALID_POSITION) { + ListContainer lastComponent = + (ListContainer) + container + .getComponentAt(lastPos) + .findComponentById(ResourceTable.Id_video_setting_option_content); + if (lastComponent != null && lastComponent.getItemProvider() != null) { + ((SettingProvider) lastComponent.getItemProvider()).removeAllItem(); + } + } + + SettingModel selectItem = (SettingModel) container.getItemProvider().getItem(position); + if (selectItem.getOptionName().equals(SettingOptionEnum.SPEED_SELECTION.getName())) { + setSpeedList(component); + } + if (selectItem.getOptionName().equals(SettingOptionEnum.RESOLUTION.getName())) { + setResolutionList(component); + } + if (selectItem.getOptionName().equals(SettingOptionEnum.EPISODES.getName())) { + setVideoEpisodesList(component); + } + + container.getItemProvider().notifyDataSetItemChanged(position); + lastPos = position; + // Sets the style of the currently selected option + Text optionName = + (Text) + container + .getComponentAt(position) + .findComponentById(ResourceTable.Id_video_setting_option_name); + optionName.setAlpha(0.9f); + optionName.setTextSize(AttrHelper.fp2px(30, slice)); + }); + } + + private void clearView() { + ListContainer settingContainer = + (ListContainer) settingView.findComponentById(ResourceTable.Id_video_setting_container); + if (lastPos != Constants.INVALID_POSITION) { + ListContainer lastComponent = + (ListContainer) + settingContainer + .getComponentAt(lastPos) + .findComponentById(ResourceTable.Id_video_setting_option_content); + if (lastComponent != null && lastComponent.getItemProvider() != null) { + ((SettingProvider) lastComponent.getItemProvider()).removeAllItem(); + } + } + ((SettingProvider) settingContainer.getItemProvider()).removeAllItem(); + clearFocus(); + } + + private void setSettingList(ListContainer settingContainer) { + List list = new ArrayList<>(); + + for (int i = 0; i < SettingOptionEnum.values().length; i++) { + SettingModel item = new SettingModel(); + item.setOptionName(SettingOptionEnum.values()[i].getName()); + item.setLever(Constants.SETTING_OPTION_LEVER1); + list.add(item); + } + VideoSettingProvider provider = new VideoSettingProvider(slice, list, 0, null); + settingContainer.setItemProvider(provider); + } + + private void setResolutionList(Component parentComponent) { + ListContainer selectComponent = + (ListContainer) parentComponent.findComponentById(ResourceTable.Id_video_setting_option_content); + List list = new ArrayList<>(); + for (int i = 0; i < ResolutionEnum.values().length; i++) { + SettingModel item = new SettingModel(); + item.setOptionName(ResolutionEnum.values()[i].getName()); + item.setLever(Constants.SETTING_OPTION_LEVER2); + list.add(item); + } + VideoSettingProvider provider = + new VideoSettingProvider(slice, list, SettingOptionEnum.RESOLUTION.ordinal(), itemOnKeyListener); + selectComponent.setItemProvider(provider); + } + + private void setSpeedList(Component parentComponent) { + ListContainer selectComponent = + (ListContainer) parentComponent.findComponentById(ResourceTable.Id_video_setting_option_content); + List list = new ArrayList<>(); + for (int i = 0; i < SpeedEnum.values().length; i++) { + SettingModel item = new SettingModel(); + item.setOptionName(SpeedEnum.values()[i].getName()); + item.setLever(Constants.SETTING_OPTION_LEVER2); + list.add(item); + } + VideoSettingProvider provider = + new VideoSettingProvider(slice, list, SettingOptionEnum.SPEED_SELECTION.ordinal(), itemOnKeyListener); + selectComponent.setItemProvider(provider); + } + + private void setVideoEpisodesList(Component parentComponent) { + ListContainer selectComponent = + (ListContainer) parentComponent.findComponentById(ResourceTable.Id_video_setting_option_content); + List list = new ArrayList<>(); + Videos videos = videoInfoService.getAllVideoInfo(); + for (int i = 0; i < videos.getDetail().size(); i++) { + VideoModel item = new VideoModel(); + item.setVideoDesc(videos.getDetail().get(i).getVideoDesc()); + item.setVideoName(videos.getVideoName()); + item.setVideoImage(i % 2 == 0 + ? ResourceTable.Media_video_preview + : ResourceTable.Media_video_preview2); + item.setVideoTitleTime(videos.getDetail().get(i).getTotalTime()); + item.setLever(Constants.SETTING_OPTION_LEVER2); + list.add(item); + } + VideoEpisodesSelectProvider provider = + new VideoEpisodesSelectProvider(slice, list, SettingOptionEnum.EPISODES.ordinal(), itemOnKeyListener); + selectComponent.setItemProvider(provider); + } + + private String getResolutionUrl(ListContainer srcContainer, Component component) { + String url = ""; + if (srcContainer.getIndexForComponent(component) == ResolutionEnum.RESOLUTION_2K.ordinal()) { + url = videoInfoService.getVideoInfoByIndex(currentPlayingIndex).getResolutions().get(1).getUrl(); + } + if (srcContainer.getIndexForComponent(component) == ResolutionEnum.RESOLUTION_4K.ordinal() + || srcContainer.getIndexForComponent(component) == ResolutionEnum.RESOLUTION_HDR.ordinal() + || srcContainer.getIndexForComponent(component) == ResolutionEnum.RESOLUTION_BD.ordinal()) { + url = videoInfoService.getVideoInfoByIndex(currentPlayingIndex).getResolutions().get(2).getUrl(); + } + if (srcContainer.getIndexForComponent(component) == ResolutionEnum.RESOLUTION_HD.ordinal() + || srcContainer.getIndexForComponent(component) == ResolutionEnum.RESOLUTION_AUTO.ordinal()) { + url = videoInfoService.getVideoInfoByIndex(currentPlayingIndex).getResolutions().get(0).getUrl(); + } + + return url; + } + + private VideoInfo getEpisodesInfo(ListContainer srcContainer, Component component) { + // The SD video address is returned by default. + currentPlayingIndex = srcContainer.getIndexForComponent(component); + return currentPlayingIndex == Constants.INVALID_POSITION + ? null + : videoInfoService.getVideoInfoByIndex(currentPlayingIndex); + } + + private void initI18NResource() { + SettingOptionEnum.EPISODES.setName( + AppUtil.getStringResource(getContext(), ResourceTable.String_setting_option_episodes)); + SettingOptionEnum.SPEED_SELECTION.setName( + AppUtil.getStringResource(getContext(), ResourceTable.String_setting_option_speed)); + SettingOptionEnum.RESOLUTION.setName( + AppUtil.getStringResource(getContext(), ResourceTable.String_setting_option_resolution)); + ResolutionEnum.RESOLUTION_AUTO.setName( + AppUtil.getStringResource(getContext(), ResourceTable.String_resolution_auto)); + ResolutionEnum.RESOLUTION_BD.setName( + AppUtil.getStringResource(getContext(), ResourceTable.String_resolution_bd)); + ResolutionEnum.RESOLUTION_HD.setName( + AppUtil.getStringResource(getContext(), ResourceTable.String_resolution_hd)); + } + + /** + * Setting operation callback event. + */ + public interface VideoSettingCallback { + /** + * setVideoSpeed + * + * @param speed speed + */ + void setVideoSpeed(float speed); + + /** + * refreshVideoPath + * + * @param url url + * @param name name + */ + void refreshVideoPath(String url, String name); + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/Constants.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/Constants.java new file mode 100644 index 00000000..a601e2f9 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/Constants.java @@ -0,0 +1,39 @@ +package com.waylau.hmos.douyin.constant; + +/** + * Constants + */ +public class Constants { + /** + * rewind step + */ + public static final int REWIND_STEP = 10000; + /** + * volume step + */ + public static final int VOLUME_STEP = 1; + /** + * Large-screen control event + */ + public static final String TV_CONTROL_EVENT = "tv.control"; + /** + * Remote Control Code + */ + public static final String KEY_CONTROL_CODE = "KEY_CONTROL_CODE"; + /** + * Remote Control Value + */ + public static final String KEY_CONTROL_VALUE = "KEY_CONTROL_VALUE"; + /** + * Level 1 Setup Options + */ + public static final String SETTING_OPTION_LEVER1 = "lever1"; + /** + * Level 2 Setup Options + */ + public static final String SETTING_OPTION_LEVER2 = "lever2"; + /** + * Invalid location + */ + public static final int INVALID_POSITION = -1; +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/ResolutionEnum.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/ResolutionEnum.java new file mode 100644 index 00000000..63839d89 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/ResolutionEnum.java @@ -0,0 +1,39 @@ +package com.waylau.hmos.douyin.constant; + +/** + * Resolution Enumeration Class + */ +public enum ResolutionEnum { + RESOLUTION_AUTO("自动", 1), + RESOLUTION_HDR("HDR", 2), + RESOLUTION_4K("4K", 3), + RESOLUTION_2K("2K", 4), + RESOLUTION_BD("蓝光", 5), + RESOLUTION_HD("清晰", 6); + + private String name; + private int value; + + ResolutionEnum(String name, int value) { + this.name = name; + this.value = value; + } + + /** + * Obtain the resolution name. + * + * @return name name + */ + public String getName() { + return name; + } + + /** + * Set the resolution Name. + * + * @param name name + */ + public void setName(String name) { + this.name = name; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/SettingOptionEnum.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/SettingOptionEnum.java new file mode 100644 index 00000000..d6b72835 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/SettingOptionEnum.java @@ -0,0 +1,30 @@ +package com.waylau.hmos.douyin.constant; + +/** + * Setting Option Enumeration Class + */ +public enum SettingOptionEnum { + EPISODES("选集", 1), + SPEED_SELECTION("倍速播放", 2), + RESOLUTION("清晰度", 3); + + private final int code; + private String name; + + SettingOptionEnum(String name, int code) { + this.name = name; + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getCode() { + return code; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/SpeedEnum.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/SpeedEnum.java new file mode 100644 index 00000000..4c0ef136 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/constant/SpeedEnum.java @@ -0,0 +1,56 @@ +package com.waylau.hmos.douyin.constant; + +/** + * Rate enumeration class + */ +public enum SpeedEnum { + SPEED_1("1x", 1), + SPEED_2("1.5x", 1.5f), + SPEED_3("2x", 2), + SPEED_4("2.5x", 2.5f), + SPEED_5("3x", 3); + + private String name; + private float value; + + SpeedEnum(String name, float value) { + this.name = name; + this.value = value; + } + + /** + * Get the rate name. + * + * @return name name + */ + public String getName() { + return name; + } + + /** + * Set the rate name. + * + * @param name name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Set the rate value. + * + * @return value value + */ + public float getValue() { + return value; + } + + /** + * Set the rate value. + * + * @param value value + */ + public void setValue(float value) { + this.value = value; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/VideoInfo.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/VideoInfo.java new file mode 100644 index 00000000..38c32fd5 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/VideoInfo.java @@ -0,0 +1,70 @@ +package com.waylau.hmos.douyin.data; + +import com.waylau.hmos.douyin.model.ResolutionModel; + +import java.util.List; + +/** + * Basic video information + */ +public class VideoInfo { + private String videoName; + + private int currentAnthology; + + private List resolutions; + + private String videoDesc; + + private boolean isTrailer; + + private String totalTime; + + public String getVideoName() { + return videoName; + } + + public void setVideoName(String videoName) { + this.videoName = videoName; + } + + public int getCurrentAnthology() { + return currentAnthology; + } + + public void setCurrentAnthology(int currentAnthology) { + this.currentAnthology = currentAnthology; + } + + public List getResolutions() { + return resolutions; + } + + public void setResolutions(List resolutions) { + this.resolutions = resolutions; + } + + public String getVideoDesc() { + return videoDesc; + } + + public void setVideoDesc(String videoDesc) { + this.videoDesc = videoDesc; + } + + public boolean isTrailer() { + return isTrailer; + } + + public void setTrailer(boolean trailer) { + isTrailer = trailer; + } + + public String getTotalTime() { + return totalTime; + } + + public void setTotalTime(String totalTime) { + this.totalTime = totalTime; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/VideoInfoService.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/VideoInfoService.java new file mode 100644 index 00000000..18bfc50b --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/VideoInfoService.java @@ -0,0 +1,141 @@ +package com.waylau.hmos.douyin.data; + +import com.waylau.hmos.douyin.constant.ResolutionEnum; +import com.waylau.hmos.douyin.model.ResolutionModel; + +import ohos.app.Context; +import ohos.global.resource.Resource; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.utils.zson.ZSONArray; +import ohos.utils.zson.ZSONObject; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Service for obtaining video information + */ +public class VideoInfoService { + private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "VideoInfoService"); + private static final int NEWS_CONTENT_SIZE = 1024; + private static final int EOF = -1; + private final Context context; + private final Videos videos; + + /** + * Service Initialization + */ + public VideoInfoService(Context context) { + this.context = context; + + String resourcePath = "resources/rawfile/videos.json"; + String videosFile = getVideosInfo(resourcePath); + + ZSONObject videosJson = ZSONObject.stringToZSON(videosFile); + videos = new Videos(); + videos.setVideoName(videosJson.getString("name")); + videos.setTotalAnthology(videosJson.getIntValue("total_anthology")); + ZSONArray anthologyDetail = videosJson.getZSONArray("anthology_detail"); + ZSONObject anthology; + List detail = new ArrayList<>(); + for (int i = 0; i < anthologyDetail.size(); i++) { + anthology = anthologyDetail.getZSONObject(i); + VideoInfo video = new VideoInfo(); + video.setVideoName(videos.getVideoName()); + video.setCurrentAnthology(anthology.getIntValue("current_anthology")); + video.setResolutions(getResolution(anthology)); + video.setTotalTime(anthology.getString("totalTime")); + video.setVideoDesc(anthology.getString("plot")); + video.setTrailer(anthology.getBooleanValue("isTrailer")); + detail.add(video); + } + videos.setDetail(detail); + } + + /** + * Obtain all video information. + * + * @return videos + */ + public Videos getAllVideoInfo() { + return videos; + } + + /** + * Obtaining Video Playback Information. + * + * @param index video index + * @return videoJson + */ + public VideoInfo getVideoInfoByIndex(int index) { + return videos.getDetail().get(index); + } + + /** + * Get videos info from json + * + * @param resourcePath video file location + * @return videoJson + */ + private String getVideosInfo(String resourcePath) { + try { + Resource resource = context.getResourceManager().getRawFileEntry(resourcePath).openRawFile(); + byte[] tmp = new byte[NEWS_CONTENT_SIZE * NEWS_CONTENT_SIZE]; + int reads = resource.read(tmp); + if (reads != EOF) { + return new String(tmp, 0, reads, StandardCharsets.UTF_8); + } + } catch (IOException ex) { + HiLog.error(LABEL, "getVideosInfo IO error"); + } + return ""; + } + + private List getResolution(ZSONObject anthology) { + List resolutions = new ArrayList<>(); + + String url = anthology.getString("ultraHdUrl"); + if (url != null && !url.isEmpty()) { + ResolutionModel resolution = new ResolutionModel(); + resolution.setName(ResolutionEnum.RESOLUTION_HDR.getName()); + resolution.setUrl(url); + resolutions.add(resolution); + } + + url = anthology.getString("fhdUrl"); + if (url != null && !url.isEmpty()) { + ResolutionModel resolution = new ResolutionModel(); + resolution.setName(ResolutionEnum.RESOLUTION_4K.getName()); + resolution.setUrl(url); + resolutions.add(resolution); + } + + url = anthology.getString("hdUrl"); + if (url != null && !url.isEmpty()) { + ResolutionModel resolution = new ResolutionModel(); + resolution.setName(ResolutionEnum.RESOLUTION_2K.getName()); + resolution.setUrl(url); + resolutions.add(resolution); + } + + url = anthology.getString("sdUrl"); + if (url != null && !url.isEmpty()) { + ResolutionModel resolution = new ResolutionModel(); + resolution.setName(ResolutionEnum.RESOLUTION_BD.getName()); + resolution.setUrl(url); + resolutions.add(resolution); + } + + url = anthology.getString("autoUrl"); + if (url != null && !url.isEmpty()) { + ResolutionModel resolution = new ResolutionModel(); + resolution.setName(ResolutionEnum.RESOLUTION_AUTO.getName()); + resolution.setUrl(url); + resolutions.add(resolution); + } + return resolutions; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/Videos.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/Videos.java new file mode 100644 index 00000000..d974612d --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/data/Videos.java @@ -0,0 +1,38 @@ +package com.waylau.hmos.douyin.data; + +import java.util.List; + +/** + * Video list + */ +public class Videos { + private String videoName; + + private int totalAnthology; + + private List detail; + + public String getVideoName() { + return videoName; + } + + public void setVideoName(String videoName) { + this.videoName = videoName; + } + + public int getTotalAnthology() { + return totalAnthology; + } + + public void setTotalAnthology(int totalAnthology) { + this.totalAnthology = totalAnthology; + } + + public List getDetail() { + return detail; + } + + public void setDetail(List detail) { + this.detail = detail; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/ResolutionModel.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/ResolutionModel.java new file mode 100644 index 00000000..7897968e --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/ResolutionModel.java @@ -0,0 +1,29 @@ +package com.waylau.hmos.douyin.model; + +import com.waylau.hmos.douyin.constant.Constants; +import com.waylau.hmos.douyin.utils.PathUtils; + +/** + * Resolution Model Class + */ +public class ResolutionModel { + private String name; + + private String url; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/SettingComponentTag.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/SettingComponentTag.java new file mode 100644 index 00000000..c761a67c --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/SettingComponentTag.java @@ -0,0 +1,24 @@ +package com.waylau.hmos.douyin.model; + +/** + * Component Tags + */ +public class SettingComponentTag { + private final String lever; + + private final int parentType; + + public SettingComponentTag(String lever, int parentType) { + this.lever = lever; + this.parentType = parentType; + } + + public String getLever() { + return lever; + } + + public int getParentType() { + return parentType; + } + +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/SettingModel.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/SettingModel.java new file mode 100644 index 00000000..2f335879 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/SettingModel.java @@ -0,0 +1,26 @@ +package com.waylau.hmos.douyin.model; + +/** + * Setting Model Class + */ +public class SettingModel { + private String optionName; + + private String lever; + + public String getOptionName() { + return optionName; + } + + public void setOptionName(String optionName) { + this.optionName = optionName; + } + + public String getLever() { + return lever; + } + + public void setLever(String lever) { + this.lever = lever; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/VideoModel.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/VideoModel.java new file mode 100644 index 00000000..87bce2e9 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/model/VideoModel.java @@ -0,0 +1,56 @@ +package com.waylau.hmos.douyin.model; + +/** + * Video Model Class + */ +public class VideoModel { + private String videoName; + + private String videoDesc; + + private String videoTitleTime; + + private int videoImage; + + private String lever; + + public String getVideoName() { + return videoName; + } + + public void setVideoName(String videoName) { + this.videoName = videoName; + } + + public String getVideoDesc() { + return videoDesc; + } + + public void setVideoDesc(String videoDesc) { + this.videoDesc = videoDesc; + } + + public String getVideoTitleTime() { + return videoTitleTime; + } + + public void setVideoTitleTime(String videoTitleTime) { + this.videoTitleTime = videoTitleTime; + } + + public int getVideoImage() { + return videoImage; + } + + public void setVideoImage(int videoImage) { + this.videoImage = videoImage; + } + + public String getLever() { + return lever; + } + + public void setLever(String lever) { + this.lever = lever; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/SettingProvider.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/SettingProvider.java new file mode 100644 index 00000000..59cc5467 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/SettingProvider.java @@ -0,0 +1,57 @@ +package com.waylau.hmos.douyin.provider; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.BaseItemProvider; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; + +/** + * Setting List Processing Class + */ +public class SettingProvider extends BaseItemProvider { + /** + * Page Slice + */ + protected AbilitySlice slice; + /** + * Parent Component + */ + protected int parentType; + /** + * Key Listening Event + */ + protected Component.KeyEventListener itemOnKeyListener; + + public SettingProvider( + AbilitySlice slice, int parentType, Component.KeyEventListener itemOnKeyListener) { + this.slice = slice; + this.parentType = parentType; + this.itemOnKeyListener = itemOnKeyListener; + } + + @Override + public int getCount() { + return 0; + } + + @Override + public Object getItem(int index) { + return index; + } + + @Override + public long getItemId(int index) { + return index; + } + + @Override + public Component getComponent(int index, Component component, ComponentContainer componentContainer) { + return null; + } + + /** + * Clear All Subcomponents + */ + public void removeAllItem() { + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/VideoEpisodesSelectProvider.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/VideoEpisodesSelectProvider.java new file mode 100644 index 00000000..cd8878e6 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/VideoEpisodesSelectProvider.java @@ -0,0 +1,84 @@ +package com.waylau.hmos.douyin.provider; + +import com.waylau.hmos.douyin.ResourceTable; +import com.waylau.hmos.douyin.model.SettingComponentTag; +import com.waylau.hmos.douyin.model.VideoModel; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.Image; +import ohos.agp.components.LayoutScatter; +import ohos.agp.components.Text; + +import java.util.List; + +/** + * Video episode selection list processing class + */ +public class VideoEpisodesSelectProvider extends SettingProvider { + private final List episodesList; + private int focusFlag = 0; + private final Component.FocusChangedListener focusChangedListener = + (component, hasFocus) -> { + Text descTxt = (Text) component.findComponentById(ResourceTable.Id_video_list_item_desc); + if (hasFocus) { + focusFlag = 1; + descTxt.setAlpha(0.9f); + } else { + descTxt.setAlpha(0.6f); + } + }; + + public VideoEpisodesSelectProvider( + AbilitySlice slice, List list, int parentType, Component.KeyEventListener itemOnKeyListener) { + super(slice, parentType, itemOnKeyListener); + this.episodesList = list; + } + + @Override + public int getCount() { + return episodesList == null ? 0 : episodesList.size(); + } + + @Override + public Object getItem(int index) { + if (episodesList != null && index >= 0 && index < episodesList.size()) { + return episodesList.get(index); + } + return null; + } + + @Override + public void removeAllItem() { + this.episodesList.clear(); + this.notifyDataChanged(); + } + + @Override + public Component getComponent(int index, Component convertComponent, ComponentContainer componentContainer) { + final Component cpt; + if (convertComponent == null) { + cpt = + LayoutScatter.getInstance(slice) + .parse(ResourceTable.Layout_video_episodes_item, null, false); + } else { + cpt = convertComponent; + } + VideoModel item = episodesList.get(index); + Image previewImg = (Image) cpt.findComponentById(ResourceTable.Id_video_list_item_preview_image); + previewImg.setPixelMap(item.getVideoImage()); + Text totalTimeTxt = (Text) cpt.findComponentById(ResourceTable.Id_video_list_item_total_time); + totalTimeTxt.setText(item.getVideoTitleTime()); + Text descTxt = (Text) cpt.findComponentById(ResourceTable.Id_video_list_item_desc); + descTxt.setText(item.getVideoDesc()); + + cpt.setTag(new SettingComponentTag(item.getLever(), parentType)); + cpt.setFocusChangedListener(focusChangedListener); + cpt.setKeyEventListener(itemOnKeyListener); + if (index == 0 && focusFlag == 0) { + cpt.requestFocus(); + } + return cpt; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/VideoSettingProvider.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/VideoSettingProvider.java new file mode 100644 index 00000000..18f5ad24 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/provider/VideoSettingProvider.java @@ -0,0 +1,128 @@ +package com.waylau.hmos.douyin.provider; + +import com.waylau.hmos.douyin.ResourceTable; +import com.waylau.hmos.douyin.constant.Constants; +import com.waylau.hmos.douyin.model.SettingComponentTag; +import com.waylau.hmos.douyin.model.SettingModel; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.agp.colors.RgbColor; +import ohos.agp.components.AttrHelper; +import ohos.agp.components.Button; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.LayoutScatter; +import ohos.agp.components.ListContainer; +import ohos.agp.components.Text; +import ohos.agp.components.element.ShapeElement; +import ohos.agp.utils.Color; + +import java.util.List; + +/** + * Video setting list processing class + */ +public class VideoSettingProvider extends SettingProvider { + private final List settingList; + private int focusFlag = 0; + private final Component.FocusChangedListener focusChangedListener = + (component, hasFocus) -> { + SettingComponentTag tag = (SettingComponentTag) component.getTag(); + if (hasFocus) { + if (Constants.SETTING_OPTION_LEVER2.equals(tag.getLever())) { + focusFlag = 1; + Button optionName = (Button) component.findComponentById( + ResourceTable.Id_video_setting_option_button); + optionName.setTextColor(new Color(Color.getIntColor("#E5000000"))); + ShapeElement background = new ShapeElement(); + background.setCornerRadius(80); + background.setRgbColor(new RgbColor(241, 243, 245, 204)); + background.setStroke(2, new RgbColor(255, 255, 255)); + component.setBackground(background); + } + if (Constants.SETTING_OPTION_LEVER1.equals(tag.getLever())) { + ListContainer parentList = + (ListContainer) slice.findComponentById(ResourceTable.Id_video_setting_container); + parentList.executeItemClick( + component, parentList.getIndexForComponent(component), component.getId()); + } + } else { + if (Constants.SETTING_OPTION_LEVER2.equals(tag.getLever())) { + Button optionName = (Button) component.findComponentById( + ResourceTable.Id_video_setting_option_button); + optionName.setTextColor(new Color(Color.getIntColor("#E5FFFFFF"))); + ShapeElement background = new ShapeElement(); + background.setCornerRadius(80); + background.setRgbColor(new RgbColor(241, 243, 245, 51)); + background.setStroke(2, new RgbColor(255, 255, 255)); + component.setBackground(background); + } + } + }; + + public VideoSettingProvider( + AbilitySlice slice, + List list, + int parentType, + Component.KeyEventListener itemOnKeyListener) { + super(slice, parentType, itemOnKeyListener); + this.settingList = list; + } + + @Override + public int getCount() { + return settingList == null ? 0 : settingList.size(); + } + + @Override + public Object getItem(int index) { + if (settingList != null && index >= 0 && index < settingList.size()) { + return settingList.get(index); + } + return null; + } + + @Override + public void removeAllItem() { + this.settingList.clear(); + this.notifyDataChanged(); + } + + @Override + public Component getComponent(int index, Component convertComponent, ComponentContainer componentContainer) { + int xmlId; + SettingModel item = settingList.get(index); + if (item.getLever().equals(Constants.SETTING_OPTION_LEVER2)) { + xmlId = ResourceTable.Layout_video_common_item; + } else { + xmlId = ResourceTable.Layout_video_setting_item; + } + Component cpt; + if (convertComponent == null) { + cpt = LayoutScatter.getInstance(slice).parse(xmlId, null, false); + } else { + cpt = convertComponent; + } + + if (item.getLever().equals(Constants.SETTING_OPTION_LEVER1)) { + Text optionName = (Text) cpt.findComponentById(ResourceTable.Id_video_setting_option_name); + optionName.setText(item.getOptionName()); + optionName.setAlpha(0.9f); + optionName.setTextSize(AttrHelper.fp2px(20, slice)); + } else { + Button optionName = (Button) cpt.findComponentById(ResourceTable.Id_video_setting_option_button); + optionName.setText(item.getOptionName()); + optionName.setAlpha(0.8f); + optionName.setTextSize(AttrHelper.fp2px(24, slice)); + } + cpt.setTag(new SettingComponentTag(item.getLever(), parentType)); + + cpt.setFocusChangedListener(focusChangedListener); + cpt.setKeyEventListener(itemOnKeyListener); + if (index == 0 && item.getLever().equals(Constants.SETTING_OPTION_LEVER2) && focusFlag == 0) { + cpt.requestFocus(); + } + + return cpt; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/slice/MainAbilitySlice.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/slice/MainAbilitySlice.java new file mode 100644 index 00000000..5029191e --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/slice/MainAbilitySlice.java @@ -0,0 +1,27 @@ +package com.waylau.hmos.douyin.slice; + +import com.waylau.hmos.douyin.ResourceTable; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; + +/** + * MainAbilitySlice + */ +public class MainAbilitySlice extends AbilitySlice { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_ability_main); + } + + @Override + public void onActive() { + super.onActive(); + } + + @Override + public void onForeground(Intent intent) { + super.onForeground(intent); + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/slice/VideoPlayAbilitySlice.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/slice/VideoPlayAbilitySlice.java new file mode 100644 index 00000000..07e76153 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/slice/VideoPlayAbilitySlice.java @@ -0,0 +1,597 @@ +package com.waylau.hmos.douyin.slice; + +import com.waylau.hmos.douyin.constant.ControlCode; +import com.waylau.hmos.douyin.constant.RemoteConstant; +import com.waylau.hmos.douyin.ResourceTable; +import com.waylau.hmos.douyin.component.VideoSetting; +import com.waylau.hmos.douyin.constant.Constants; +import com.waylau.hmos.douyin.data.VideoInfo; +import com.waylau.hmos.douyin.data.VideoInfoService; +import com.waylau.hmos.douyin.utils.AppUtil; +import com.waylau.hmos.douyin.view.VideoPlayerSlider; +import com.waylau.hmos.douyin.player.core.VideoPlayer.HmPlayerAdapter; +import com.waylau.hmos.douyin.player.ui.widget.media.VideoPlayerView; +import com.waylau.hmos.douyin.player.view.IBaseComponentAdapter; +import com.waylau.hmos.douyin.player.view.ITitleAdapter; +import com.waylau.hmos.douyin.player.view.VideoBoxArea; +import com.waylau.hmos.douyin.remote.MyRemoteProxy; +import com.waylau.hmos.douyin.utils.ScreenUtils; + +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.ability.IAbilityConnection; +import ohos.aafwk.content.Intent; +import ohos.aafwk.content.Operation; +import ohos.agp.components.AttrHelper; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.DirectionalLayout; +import ohos.agp.components.Image; +import ohos.agp.components.StackLayout; +import ohos.agp.components.Text; +import ohos.agp.utils.Color; +import ohos.agp.utils.LayoutAlignment; +import ohos.agp.window.dialog.ToastDialog; +import ohos.agp.window.service.Window; +import ohos.agp.window.service.WindowManager; +import ohos.bundle.AbilityInfo; +import ohos.bundle.ElementName; +import ohos.event.commonevent.CommonEventData; +import ohos.event.commonevent.CommonEventManager; +import ohos.event.commonevent.CommonEventSubscribeInfo; +import ohos.event.commonevent.CommonEventSubscriber; +import ohos.event.commonevent.CommonEventSupport; +import ohos.event.commonevent.MatchingSkills; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; +import ohos.multimodalinput.event.KeyEvent; +import ohos.rpc.IRemoteObject; +import ohos.rpc.RemoteException; + +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Video Playback Page + */ +public class VideoPlayAbilitySlice extends AbilitySlice { + private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "VideoPlayAbilitySlice"); + /** + * Demo File Path + */ + private static final String DEFAULT_PATH = "entrytv/resources/base/media/hm_sample_pv.mp4"; + + private static final String REMOTE_PHONE_ABILITY = + "com.waylau.hmos.douyin.ability.SyncControlServiceAbility"; + private static MyRemoteProxy myProxy; + // Creating a Connection Callback Instance + private final IAbilityConnection connection = + new IAbilityConnection() { + // Callback for connecting to a service + @Override + public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) { + myProxy = new MyRemoteProxy(iRemoteObject); + } + + // Callback for disconnecting from the service + @Override + public void onAbilityDisconnectDone(ElementName elementName, int resultCode) { + disconnectAbility(this); + } + }; + private VideoInfoService videoInfoService; + private VideoPlayerView videoBox; + private VideoSetting videoSetting; + private int currentPlayingIndex = 0; + private MyCommonEventSubscriber tvSubscriber; + /** + * Timer for refresh the progress. + */ + private Timer timer; + private VideoPlayerView.RemoteControlCallback remoteControlCallback = + new VideoPlayerView.RemoteControlCallback() { + @Override + public void onProgressChanged(long totalTime, int progress) { + if (myProxy != null) { + Map progressValue = new HashMap<>(); + progressValue.put(RemoteConstant.REMOTE_KEY_VIDEO_TOTAL_TIME, String.valueOf(totalTime)); + progressValue.put(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PROGRESS, String.valueOf(progress)); + myProxy.sendDataToRemote( + RemoteConstant.REQUEST_SYNC_VIDEO_STATUS, + ControlCode.SYNC_VIDEO_PROCESS.getCode(), + progressValue); + } + } + + @Override + public void onPlayingStatusChanged(boolean isPlaying) { + if (myProxy != null) { + Map videoStatusMap = new HashMap<>(); + videoStatusMap.put( + RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_PLAYBACK_STATUS, String.valueOf(isPlaying)); + HiLog.debug(LABEL, "isPlaying = " + String.valueOf(isPlaying)); + myProxy.sendDataToRemote( + RemoteConstant.REQUEST_SYNC_VIDEO_STATUS, + ControlCode.SYNC_VIDEO_STATUS.getCode(), + videoStatusMap); + } + } + + @Override + public void onVolumeChanged(int volume) { + if (myProxy != null) { + Map volumeMap = new HashMap<>(); + volumeMap.put(RemoteConstant.REMOTE_KEY_VIDEO_CURRENT_VOLUME, String.valueOf(volume)); + myProxy.sendDataToRemote( + RemoteConstant.REQUEST_SYNC_VIDEO_STATUS, + ControlCode.SYNC_VIDEO_VOLUME.getCode(), + volumeMap); + } + } + }; + /** + * Whether to resume playback + */ + private boolean needResumeStatus; + + @Override + protected void onStart(Intent intent) { + if (!ScreenUtils.isPortrait(getContext())) { + hideStatusBar(); + } else { + showStatusBar(); + } + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_ability_video_box); + + videoInfoService = new VideoInfoService(this); + videoBox = (VideoPlayerView) findComponentById(ResourceTable.Id_video_view); + + if (videoBox != null) { + currentPlayingIndex = intent.getIntParam(RemoteConstant.INTENT_PARAM_REMOTE_VIDEO_INDEX, 0); + videoBox.setClickToHideControlArea(false); + addCoreComponent(); + addCustomComponent(); + videoBox.registerRemoteControlCallback(remoteControlCallback); + String path = intent.getStringParam(RemoteConstant.INTENT_PARAM_REMOTE_VIDEO_PATH); + if (path != null) { + videoBox.setVideoPath( + videoInfoService.getVideoInfoByIndex(currentPlayingIndex).getResolutions().get(0).getUrl()); + videoBox.setPlayerOnPreparedListener( + () -> { + videoBox.start(); + videoBox.seekTo(intent.getIntParam(RemoteConstant.INTENT_PARAM_REMOTE_START_POSITION, 0)); + }); + } else { + videoBox.setVideoPath(DEFAULT_PATH); + } + videoBox.setErrorListener( + (errorType, errorCode) -> { + ToastDialog toast = new ToastDialog(getContext()); + switch (errorType) { + case HmPlayerAdapter.ERROR_LOADING_RESOURCE: + toast.setText( + AppUtil.getStringResource( + getContext(), ResourceTable.String_media_file_loading_error)); + break; + case HmPlayerAdapter.ERROR_INVALID_OPERATION: + toast.setText( + AppUtil.getStringResource( + getContext(), ResourceTable.String_invalid_operation)); + break; + default: + toast.setText( + AppUtil.getStringResource( + getContext(), ResourceTable.String_undefined_error_type)); + break; + } + getUITaskDispatcher().asyncDispatch(toast::show); + }); + } + + initSettingComponent(); + connectRemoteDevice(intent.getStringParam(RemoteConstant.INTENT_PARAM_REMOTE_DEVICE_ID)); + subscribe(); + } + + @Override + protected void onOrientationChanged(AbilityInfo.DisplayOrientation displayOrientation) { + super.onOrientationChanged(displayOrientation); + // The length and width of the VideoBox must be recalculated when the landscape and portrait are switched. + if (videoBox != null) { + videoBox.onOrientationChanged(displayOrientation); + } + if (displayOrientation == AbilityInfo.DisplayOrientation.LANDSCAPE) { + hideStatusBar(); + } else { + showStatusBar(); + } + } + + private void hideStatusBar() { + getWindow().addFlags(WindowManager.LayoutConfig.MARK_FULL_SCREEN); + } + + private void showStatusBar() { + getWindow().clearFlags(WindowManager.LayoutConfig.MARK_FULL_SCREEN); + } + + @Override + protected void onForeground(Intent intent) { + super.onForeground(intent); + if (videoBox != null && needResumeStatus) { + videoBox.registerRemoteControlCallback(remoteControlCallback); + videoBox.start(); + needResumeStatus = false; + } + } + + @Override + protected void onBackground() { + super.onBackground(); + if (videoBox != null) { + if (videoBox.isPlaying()) { + needResumeStatus = true; + } + videoBox.unregisterRemoteControlCallback(); + videoBox.pause(); + } + } + + @Override + protected void onBackPressed() { + if (videoSetting.isShow()) { + videoSetting.hide(); + return; + } + super.onBackPressed(); + } + + @Override + protected void onStop() { + super.onStop(); + if (videoBox != null) { + videoBox.release(true); + videoBox.unregisterRemoteControlCallback(); + } + if (timer != null) { + timer.cancel(); + } + unSubscribe(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { + Window window = getWindow(); + if (window == null) { + return super.onKeyDown(keyCode, keyEvent); + } + if (keyCode == KeyEvent.KEY_VOLUME_UP) { + videoBox.setVolume(Constants.VOLUME_STEP); + return true; + } + if (keyCode == KeyEvent.KEY_VOLUME_DOWN) { + videoBox.setVolume(-Constants.VOLUME_STEP); + return true; + } + if (videoSetting.isShow()) { + return true; + } + videoBox.simulateDrag(); + if (keyCode == KeyEvent.KEY_ENTER || keyCode == KeyEvent.KEY_DPAD_CENTER) { + if (videoBox.isPlaying()) { + videoBox.pause(); + } else { + videoBox.start(); + } + return true; + } + if (keyCode == KeyEvent.KEY_DPAD_LEFT) { + videoBox.seekTo(videoBox.getCurrentPosition() - Constants.REWIND_STEP); + return true; + } + if (keyCode == KeyEvent.KEY_DPAD_RIGHT) { + videoBox.seekTo(videoBox.getCurrentPosition() + Constants.REWIND_STEP); + return true; + } + + return super.onKeyDown(keyCode, keyEvent); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { + if (timer != null) { + timer.cancel(); + } + Window window = getWindow(); + if (window == null) { + return super.onKeyUp(keyCode, keyEvent); + } + if (keyCode == KeyEvent.KEY_DPAD_DOWN && videoSetting != null) { + videoSetting.show(); + return true; + } + return super.onKeyUp(keyCode, keyEvent); + } + + @Override + public boolean onKeyPressAndHold(int keyCode, KeyEvent keyEvent) { + Window window = getWindow(); + if (window == null || videoSetting.isShow()) { + return super.onKeyPressAndHold(keyCode, keyEvent); + } + if (keyCode == KeyEvent.KEY_DPAD_LEFT || keyCode == KeyEvent.KEY_DPAD_RIGHT) { + // When you press and hold the fast forward or rewind key, + // the timer is started and the progress is refreshed every 500 ms. + TimerTask timerTask = + new TimerTask() { + @Override + public void run() { + int rewindStep = + keyCode == KeyEvent.KEY_DPAD_RIGHT ? Constants.REWIND_STEP : -Constants.REWIND_STEP; + videoBox.seekTo(videoBox.getCurrentPosition() + rewindStep); + } + }; + timer = new Timer(); + timer.schedule(timerTask, 100, 500); + return true; + } + + return super.onKeyPressAndHold(keyCode, keyEvent); + } + + /** + * Adding core component like playback button and seek bar + */ + private void addCoreComponent() { + // Add Progress Bar + videoBox.addSeekBar(new VideoPlayerSlider(getContext()), VideoBoxArea.BOTTOM, 0); + } + + /** + * Adding no-core component like share button and title…… + */ + private void addCustomComponent() { + // Add title and back button + addTitle(); + + // Add more settings component + addMoreSetting(); + } + + private void addTitle() { + videoBox.addTitle( + new ITitleAdapter() { + private Text title; + + @Override + public Text initComponent() { + title = new Text(getContext()); + title.setMaxTextLines(1); + title.setTextColor(new Color(getColor(ResourceTable.Color_tv_video_title_color))); + title.setAutoFontSize(true); + title.setAutoFontSizeRule( + AttrHelper.fp2px(10, getContext()), + AttrHelper.fp2px(20, getContext()), + AttrHelper.fp2px(2, getContext())); + title.setText(videoInfoService.getVideoInfoByIndex(currentPlayingIndex).getVideoDesc()); + return title; + } + + @Override + public void onTitleChange(String str) { + if (title != null) { + title.setText(str); + } + } + + @Override + public void onOrientationChanged( + AbilityInfo.DisplayOrientation displayOrientation, + ComponentContainer from, + ComponentContainer to) { + if (title == null) { + return; + } + // Title placeholder required,cannot be hidden. + title.setVisibility( + displayOrientation == AbilityInfo.DisplayOrientation.PORTRAIT + ? Component.INVISIBLE + : Component.VISIBLE); + } + + @Override + public void onVideoSourceChanged() { + } + + @Override + public void onClick(Component component) { + } + + @Override + public DirectionalLayout.LayoutConfig initLayoutConfig() { + return null; + } + }, + VideoBoxArea.TOP); + } + + private void addMoreSetting() { + videoBox.addComponent( + new IBaseComponentAdapter() { + private DirectionalLayout moreSetting; + + @Override + public Component initComponent() { + moreSetting = new DirectionalLayout(getContext()); + moreSetting.setOrientation(Component.HORIZONTAL); + moreSetting.setAlignment(LayoutAlignment.CENTER); + Text textBef = new Text(getContext()); + textBef.setText(ResourceTable.String_videobox_title_tips_pre); + textBef.setTextColor(new Color(getColor(ResourceTable.Color_tv_more_setting_icon_color))); + textBef.setTextSize(AttrHelper.fp2px(18, getContext())); + Image image = new Image(getContext()); + image.setPixelMap(ResourceTable.Media_ic_down); + image.setComponentSize(AttrHelper.fp2px(16, getContext()), AttrHelper.fp2px(18, getContext())); + image.setScaleMode(Image.ScaleMode.STRETCH); + Text textAft = new Text(getContext()); + textAft.setText(ResourceTable.String_videobox_title_tips_after); + textAft.setTextColor(new Color(getColor(ResourceTable.Color_tv_more_setting_icon_color))); + textAft.setTextSize(AttrHelper.fp2px(18, getContext())); + moreSetting.addComponent(textBef); + moreSetting.addComponent(image); + moreSetting.addComponent(textAft); + return moreSetting; + } + + @Override + public void onClick(Component component) { + } + + @Override + public DirectionalLayout.LayoutConfig initLayoutConfig() { + return new DirectionalLayout.LayoutConfig( + ComponentContainer.LayoutConfig.MATCH_CONTENT, + ComponentContainer.LayoutConfig.MATCH_CONTENT); + } + + @Override + public void onOrientationChanged( + AbilityInfo.DisplayOrientation displayOrientation, + ComponentContainer from, + ComponentContainer to) { + if (moreSetting == null) { + return; + } + moreSetting.setVisibility( + displayOrientation == AbilityInfo.DisplayOrientation.PORTRAIT + ? Component.INVISIBLE + : Component.VISIBLE); + } + + @Override + public void onVideoSourceChanged() { + } + }, + VideoBoxArea.TOP, + AttrHelper.vp2px(24, getContext())); + } + + private void connectRemoteDevice(String deviceId) { + Intent connectPaIntent = new Intent(); + Operation operation = + new Intent.OperationBuilder() + .withDeviceId(deviceId) + .withBundleName(getBundleName()) + .withAbilityName(REMOTE_PHONE_ABILITY) + .withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE) + .build(); + connectPaIntent.setOperation(operation); + + connectAbility(connectPaIntent, connection); + } + + private void subscribe() { + MatchingSkills matchingSkills = new MatchingSkills(); + matchingSkills.addEvent(Constants.TV_CONTROL_EVENT); + matchingSkills.addEvent(CommonEventSupport.COMMON_EVENT_SCREEN_ON); + CommonEventSubscribeInfo subscribeInfo = new CommonEventSubscribeInfo(matchingSkills); + tvSubscriber = new MyCommonEventSubscriber(subscribeInfo); + try { + CommonEventManager.subscribeCommonEvent(tvSubscriber); + } catch (RemoteException e) { + HiLog.error(LABEL, "subscribeCommonEvent occur exception."); + } + } + + private void unSubscribe() { + try { + CommonEventManager.unsubscribeCommonEvent(tvSubscriber); + } catch (RemoteException e) { + HiLog.error(LABEL, "unSubscribe Exception"); + } + } + + private void initSettingComponent() { + videoSetting = new VideoSetting(this, videoInfoService); + videoSetting.registerVideoSettingCallback( + new VideoSetting.VideoSettingCallback() { + @Override + public void setVideoSpeed(float speed) { + videoBox.setPlaybackSpeed(speed); + videoSetting.hide(); + } + + @Override + public void refreshVideoPath(String url, String name) { + videoBox.pause(); + if (!"".equals(name)) { + videoBox.setVideoPathAndTitle(url, name); + } else { + videoBox.setVideoPath(url); + } + videoBox.setPlayerOnPreparedListener(() -> videoBox.start()); + videoSetting.hide(); + } + }); + StackLayout rootLayout = (StackLayout) findComponentById(ResourceTable.Id_root_layout); + rootLayout.addComponent(videoSetting); + } + + class MyCommonEventSubscriber extends CommonEventSubscriber { + MyCommonEventSubscriber(CommonEventSubscribeInfo info) { + super(info); + } + + @Override + public void onReceiveEvent(CommonEventData commonEventData) { + HiLog.info(LABEL, "onReceiveEvent....."); + Intent intent = commonEventData.getIntent(); + int controlCode = intent.getIntParam(Constants.KEY_CONTROL_CODE, 0); + String extras = intent.getStringParam(Constants.KEY_CONTROL_VALUE); + if (controlCode == ControlCode.PLAY.getCode()) { + if (videoBox.isPlaying()) { + videoBox.pause(); + } else if (!videoBox.isPlaying() && !needResumeStatus) { + videoBox.start(); + } else { + HiLog.error(LABEL, "Ignoring the case with player status"); + } + } else if (controlCode == ControlCode.SEEK.getCode()) { + videoBox.seekTo(videoBox.getDuration() * Integer.parseInt(extras) / 100); + } else if (controlCode == ControlCode.FORWARD.getCode()) { + videoBox.seekTo(videoBox.getCurrentPosition() + Constants.REWIND_STEP); + } else if (controlCode == ControlCode.BACKWARD.getCode()) { + videoBox.seekTo(videoBox.getCurrentPosition() - Constants.REWIND_STEP); + } else if (controlCode == ControlCode.VOLUME_ADD.getCode()) { + videoBox.setVolume(Constants.VOLUME_STEP); + } else if (controlCode == ControlCode.VOLUME_REDUCED.getCode()) { + videoBox.setVolume(-Constants.VOLUME_STEP); + } else if (controlCode == ControlCode.SWITCH_SPEED.getCode()) { + videoBox.setPlaybackSpeed(Float.parseFloat(extras)); + } else if (controlCode == ControlCode.SWITCH_RESOLUTION.getCode()) { + long currentPosition = videoBox.getCurrentPosition(); + int resolutionIndex = Integer.parseInt(extras); + VideoInfo videoInfo = videoInfoService.getVideoInfoByIndex(currentPlayingIndex); + videoBox.pause(); + videoBox.setVideoPath(videoInfo.getResolutions().get(resolutionIndex).getUrl()); + videoBox.setPlayerOnPreparedListener( + () -> { + videoBox.seekTo(currentPosition); + videoBox.start(); + }); + } else if (controlCode == ControlCode.SWITCH_VIDEO.getCode()) { + videoBox.pause(); + currentPlayingIndex = Integer.parseInt(extras); + VideoInfo videoInfo = videoInfoService.getVideoInfoByIndex(currentPlayingIndex); + videoBox.setVideoPathAndTitle(videoInfo.getResolutions().get(0).getUrl(), videoInfo.getVideoDesc()); + videoBox.setPlayerOnPreparedListener(() -> videoBox.start()); + } else if (controlCode == ControlCode.STOP_CONNECTION.getCode()) { + terminate(); + } else { + HiLog.error(LABEL, "Ignoring the case with control code"); + } + } + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/utils/AppUtil.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/utils/AppUtil.java new file mode 100644 index 00000000..9d220591 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/utils/AppUtil.java @@ -0,0 +1,36 @@ +package com.waylau.hmos.douyin.utils; + +import ohos.app.Context; +import ohos.global.resource.NotExistException; +import ohos.global.resource.WrongTypeException; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import java.io.IOException; + +/** + * AppUtil + */ +public class AppUtil { + private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "AppUtil"); + + /** + * Get string resource + * + * @param context context + * @param id id + * @return String + */ + public static String getStringResource(Context context, int id) { + try { + return context.getResourceManager().getElement(id).getString(); + } catch (IOException e) { + HiLog.info(LABEL, "IOException"); + } catch (NotExistException e) { + HiLog.info(LABEL, "NotExistException"); + } catch (WrongTypeException e) { + HiLog.info(LABEL, "WrongTypeException"); + } + return ""; + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/view/VideoPlayerPlaybackButton.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/view/VideoPlayerPlaybackButton.java new file mode 100644 index 00000000..f708f65e --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/view/VideoPlayerPlaybackButton.java @@ -0,0 +1,75 @@ +package com.waylau.hmos.douyin.view; + +import com.waylau.hmos.douyin.ResourceTable; +import com.waylau.hmos.douyin.player.core.PlayerStatus; +import com.waylau.hmos.douyin.player.view.IPlaybackButtonAdapter; +import com.waylau.hmos.douyin.utils.ElementUtils; + +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.DirectionalLayout; +import ohos.agp.components.element.Element; +import ohos.app.Context; +import ohos.bundle.AbilityInfo; + +/** + * VideoPlayerView playback button + */ +public class VideoPlayerPlaybackButton extends Component implements IPlaybackButtonAdapter { + public VideoPlayerPlaybackButton(Context context) { + super(context); + } + + @Override + public Element getPlaybackElement() { + return ElementUtils.getElementByResId(getContext(), ResourceTable.Media_ic_circular_play); + } + + @Override + public Element getPauseElement() { + return ElementUtils.getElementByResId(getContext(), ResourceTable.Media_ic_circular_pause); + } + + @Override + public Component initComponent() { + setBackground(getPlaybackElement()); + return this; + } + + @Override + public void onClick(Component component) { + } + + @Override + public DirectionalLayout.LayoutConfig initLayoutConfig() { + return null; + } + + @Override + public void onOrientationChanged( + AbilityInfo.DisplayOrientation displayOrientation, ComponentContainer from, ComponentContainer to) { + } + + @Override + public void onVideoSourceChanged() { + } + + @Override + public void onPlayStatusChange(PlayerStatus status) { + switch (status) { + case COMPLETE: + case PAUSE: + case STOP: + case PREPARING: + case BUFFERING: + case PREPARED: + case ERROR: + case IDLE: + setBackground(getPlaybackElement()); + break; + case PLAY: + setBackground(getPauseElement()); + break; + } + } +} diff --git a/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/view/VideoPlayerSlider.java b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/view/VideoPlayerSlider.java new file mode 100644 index 00000000..bbdb5450 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/java/com/waylau/hmos/douyin/view/VideoPlayerSlider.java @@ -0,0 +1,180 @@ +package com.waylau.hmos.douyin.view; + +import com.waylau.hmos.douyin.ResourceTable; +import com.waylau.hmos.douyin.player.view.ISliderAdapter; + +import ohos.agp.components.AttrSet; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.DependentLayout; +import ohos.agp.components.DirectionalLayout; +import ohos.agp.components.LayoutScatter; +import ohos.agp.components.Slider; +import ohos.agp.components.Text; +import ohos.app.Context; +import ohos.bundle.AbilityInfo; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * VideoPlayerView slider + */ + +public class VideoPlayerSlider extends DependentLayout implements ISliderAdapter { + /** + * Slider max value + */ + public static final int MAX_VALUE = 100; + /** + * Slider min value + */ + public static final int MIN_VALUE = 0; + /** + * Invalid value. The time or progress accepts this value and does not refresh. + */ + public static final int INVALID_VALUE = -1; + + private static final HiLogLabel LABEL = new HiLogLabel(0, 0, "VideoPlayerSlider"); + private final Component parentComponent; + private final Slider seekBar; + private final Text currentTimeText; + private final Text endTimeText; + private Date date; + private SimpleDateFormat dateFormat; + /** + * Time format. The hour is not hidden based on the media resource duration. + */ + private String pattern; + /** + * Indicates the end timestamp, in milliseconds. + */ + private long endTime; + + public VideoPlayerSlider(Context context) { + this(context, null); + } + + public VideoPlayerSlider(Context context, AttrSet attrSet) { + this(context, attrSet, null); + } + + public VideoPlayerSlider(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + parentComponent = + LayoutScatter.getInstance(context) + .parse(ResourceTable.Layout_view_video_box_seek_bar_style1, this, true); + seekBar = (Slider) parentComponent.findComponentById(ResourceTable.Id_seek_bar); + currentTimeText = (Text) parentComponent.findComponentById(ResourceTable.Id_current_time); + endTimeText = (Text) parentComponent.findComponentById(ResourceTable.Id_end_time); + } + + @Override + public Component initComponent() { + seekBar.setMinValue(MIN_VALUE); + seekBar.setMaxValue(MAX_VALUE); + currentTimeText.setText("00:00"); + return parentComponent; + } + + @Override + public void onClick(Component component) { + } + + @Override + public DirectionalLayout.LayoutConfig initLayoutConfig() { + return null; + } + + @Override + public void onOrientationChanged( + AbilityInfo.DisplayOrientation displayOrientation, + ComponentContainer fromLayout, + ComponentContainer toLayout) { + } + + @Override + public void onVideoSourceChanged() { + } + + @Override + public void onValueChanged(Slider.ValueChangedListener listener) { + seekBar.setValueChangedListener(listener); + } + + @Override + public synchronized void onMediaProgressChanged(long currentTime, int progress) { + getContext() + .getUITaskDispatcher() + .asyncDispatch( + () -> { + if (progress >= MIN_VALUE && progress <= MAX_VALUE) { + seekBar.setProgressValue(progress); + } else if (progress == INVALID_VALUE) { + HiLog.debug(LABEL, "Not refresh progress"); + } else { + HiLog.error(LABEL, "Invalid progress = " + progress + " in onMediaProgressChange()"); + } + + if (currentTime >= MIN_VALUE && currentTime <= endTime) { + currentTimeText.setText(ms2TimeString(currentTime)); + } else if (currentTime == INVALID_VALUE) { + HiLog.debug(LABEL, "Not refresh current time"); + } else { + HiLog.error(LABEL, "Invalid currentTime = " + currentTime + + " in onMediaProgressChange()"); + } + }); + } + + @Override + public void onBufferProgressChanged(int percent) { + getContext().getUITaskDispatcher().asyncDispatch(() -> { + seekBar.setViceProgress(percent); + }); + } + + @Override + public void initMediaEndTime(long endTime) { + this.endTime = endTime; + setPattern(endTime); + getContext().getUITaskDispatcher().asyncDispatch(() -> endTimeText.setText(ms2TimeString(endTime))); + } + + @Override + public void clearEndTime() { + endTimeText.setText(""); + } + + /** + * Sets the pattern of the formatter to determine whether to display hour based on the media file duration. + * + * @param endTime Media file duration + */ + private void setPattern(long endTime) { + if (endTime > 60 * 60 * 1000 - 1) { + pattern = "HH:mm:ss"; + } else { + pattern = "mm:ss"; + } + } + + /** + * Millisecond timestamp-to-time character string + * + * @param ms millisecond timestamp + * @return Time string + */ + private String ms2TimeString(long ms) { + if (dateFormat == null) { + dateFormat = new SimpleDateFormat(pattern); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT+00:00")); + date = new Date(); + } + date.setTime(ms); + return dateFormat.format(date); + } +} diff --git a/samples/Douyin/entrytv/src/main/resources/base/element/color.json b/samples/Douyin/entrytv/src/main/resources/base/element/color.json new file mode 100644 index 00000000..697e6b0c --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/element/color.json @@ -0,0 +1,32 @@ +{ + "color": [ + { + "name": "default_white_color", + "value": "#FFFFFF" + }, + { + "name": "default_black_color", + "value": "#000000" + }, + { + "name": "seek_bar_progress_color", + "value": "#F1F3F5" + }, + { + "name": "seek_bar_vice_progress_color", + "value": "#33F1F3F5" + }, + { + "name": "seek_bar_background_instruct_color", + "value": "#19F1F3F5" + }, + { + "name": "tv_video_title_color", + "value": "#CCCCCC" + }, + { + "name": "tv_more_setting_icon_color", + "value": "#A6AFB0" + } + ] +} \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/element/string.json b/samples/Douyin/entrytv/src/main/resources/base/element/string.json new file mode 100644 index 00000000..44f8c7b0 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/element/string.json @@ -0,0 +1,48 @@ +{ + "string": [ + { + "name": "entrytv_MainAbility", + "value": "entrytv_MainAbility" + }, + { + "name": "mainability_description", + "value": "Java_TV_Video Player Ability" + }, + { + "name": "HelloWorld", + "value": "Hello World" + }, + { + "name": "videobox_title_tips_pre", + "value": "Press the" + }, + { + "name": "videobox_title_tips_after", + "value": "to enter more settings" + }, + { + "name": "setting_option_episodes", + "value": "Episodes" + }, + { + "name": "setting_option_speed", + "value": "Playing Speed" + }, + { + "name": "setting_option_resolution", + "value": "Definition" + }, + { + "name": "resolution_auto", + "value": "Auto" + }, + { + "name": "resolution_bd", + "value": "Blu-Ray" + }, + { + "name": "resolution_hd", + "value": "SD" + } + ] +} \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/graphic/background_ability_main.xml b/samples/Douyin/entrytv/src/main/resources/base/graphic/background_ability_main.xml new file mode 100644 index 00000000..bee0d060 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/graphic/background_ability_main.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/graphic/background_episodes_setting_item.xml b/samples/Douyin/entrytv/src/main/resources/base/graphic/background_episodes_setting_item.xml new file mode 100644 index 00000000..beded378 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/graphic/background_episodes_setting_item.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/graphic/background_setting_item.xml b/samples/Douyin/entrytv/src/main/resources/base/graphic/background_setting_item.xml new file mode 100644 index 00000000..4c8d99e5 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/graphic/background_setting_item.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/graphic/hm_sample_slider_thumb.xml b/samples/Douyin/entrytv/src/main/resources/base/graphic/hm_sample_slider_thumb.xml new file mode 100644 index 00000000..bed8bcc4 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/graphic/hm_sample_slider_thumb.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/layout/ability_main.xml b/samples/Douyin/entrytv/src/main/resources/base/layout/ability_main.xml new file mode 100644 index 00000000..9c8498dd --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/layout/ability_main.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/layout/ability_video_box.xml b/samples/Douyin/entrytv/src/main/resources/base/layout/ability_video_box.xml new file mode 100644 index 00000000..a16d3d19 --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/layout/ability_video_box.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/samples/Douyin/entrytv/src/main/resources/base/layout/video_common_item.xml b/samples/Douyin/entrytv/src/main/resources/base/layout/video_common_item.xml new file mode 100644 index 00000000..8fc3d1bf --- /dev/null +++ b/samples/Douyin/entrytv/src/main/resources/base/layout/video_common_item.xml @@ -0,0 +1,21 @@ + + + + + + + + +