@buaa_smat/hometrans 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +141 -124
  2. package/agents/build-fixer.md +1 -0
  3. package/agents/code-review-fix.md +1 -0
  4. package/agents/code-reviewer.md +1 -0
  5. package/agents/logic-coding.md +1 -0
  6. package/agents/logic-context-builder.md +1 -0
  7. package/agents/review-fixer.md +1 -0
  8. package/agents/self-test-fixer.md +1 -0
  9. package/agents/self-tester.md +260 -233
  10. package/agents/spec-generator.md +1 -0
  11. package/agents/test-tools/autotest/README.md +223 -0
  12. package/agents/test-tools/autotest/config.yaml.example +58 -0
  13. package/agents/test-tools/autotest/pyproject.toml +16 -0
  14. package/agents/test-tools/autotest/report_tool.py +759 -0
  15. package/agents/test-tools/autotest/self_test_runner.py +773 -0
  16. package/agents/test-tools/autotest/testcases_schema.md +143 -0
  17. package/agents/test-tools/autotest/testcases_tool.py +215 -0
  18. package/agents/test-tools/autotest/uv.lock +3156 -0
  19. package/agents/test-tools/harmony_autotest-0.1.0-py3-none-any.whl +0 -0
  20. package/agents/test-tools/hypium-6.1.0.210-py3-none-any.whl +0 -0
  21. package/agents/test-tools/hypium_mcp-0.6.5-py3-none-any.whl +0 -0
  22. package/agents/test-tools/xdevice-6.1.0.210-py3-none-any.whl +0 -0
  23. package/agents/test-tools/xdevice_devicetest-6.1.0.210-py3-none-any.whl +0 -0
  24. package/agents/test-tools/xdevice_ohos-6.1.0.210-py3-none-any.whl +0 -0
  25. package/dist/cli/config-store.js +27 -2
  26. package/dist/cli/config.js +17 -6
  27. package/dist/cli/index.js +3 -2
  28. package/dist/cli/init.js +135 -22
  29. package/dist/cli/mcp.js +2 -2
  30. package/dist/context/index.js +165 -69
  31. package/package.json +59 -60
  32. package/skills/code-dev-review-fix/SKILL.md +279 -0
  33. package/skills/code-dev-review-fix-workspace/evals/evals.json +56 -0
  34. package/skills/code-dev-review-fix-workspace/iteration-1/routing-results.md +23 -0
  35. package/skills/convert_pipeline/SKILL.md +423 -439
  36. package/skills/hmos-resources-convert/SKILL.md +623 -0
  37. package/skills/hmos-resources-convert/evals/evals.json +171 -0
  38. package/skills/hmos-resources-convert/references/conversion-rules.md +663 -0
  39. package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -0
  40. package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -0
  41. package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -0
  42. package/skills/hmos-resources-convert/template/AppScope/app.json5 +10 -0
  43. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +8 -0
  44. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  45. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  46. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +7 -0
  47. package/skills/hmos-resources-convert/template/build-profile.json5 +42 -0
  48. package/skills/hmos-resources-convert/template/code-linter.json5 +32 -0
  49. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +33 -0
  50. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +6 -0
  51. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +23 -0
  52. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +10 -0
  53. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +48 -0
  54. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +16 -0
  55. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +23 -0
  56. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +55 -0
  57. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +8 -0
  58. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +8 -0
  59. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +16 -0
  60. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  61. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  62. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +7 -0
  63. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  64. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +3 -0
  65. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +5 -0
  66. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +8 -0
  67. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +2 -0
  68. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
  69. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +5 -0
  70. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +16 -0
  71. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +5 -0
  72. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +33 -0
  73. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +23 -0
  74. package/skills/hmos-resources-convert/template/hvigorfile.ts +6 -0
  75. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +28 -0
  76. package/skills/hmos-resources-convert/template/oh-package.json5 +10 -0
  77. package/skills/hmos-resources-convert/tools/apktool.bat +85 -0
  78. package/skills/hmos-resources-convert/tools/apktool_3.0.1.jar +0 -0
  79. package/skills/hmos-ui-align/SKILL.md +182 -0
  80. package/skills/hmos-ui-align/config-example.json +11 -0
  81. package/skills/hmos-ui-align/config.json +11 -0
  82. package/skills/hmos-ui-align/diff_analysis.md +53 -0
  83. package/skills/hmos-ui-align/page_align.md +62 -0
  84. package/skills/hmos-ui-align/readme.md +231 -0
  85. package/skills/hmos-ui-align/references/Comparison_Template.md +2 -0
  86. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  87. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  88. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  89. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  90. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  91. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  92. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  93. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  94. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  95. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  96. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  97. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  98. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  99. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  100. package/skills/hmos-ui-align/references/UI_Analysis_Template.md +4 -0
  101. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  102. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  103. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  104. package/skills/hmos-ui-align/scripts/app_feature_verify.py +443 -0
  105. package/skills/hmos-ui-align/scripts/navigation-capure.md +37 -0
  106. package/skills/hmos-ui-align/scripts/page_capture.py +592 -0
  107. package/skills/hmos-ui-align-batch/SKILL.md +99 -0
  108. package/skills/hmos-ui-align-batch/references/conversion-procedure.md +180 -0
  109. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  110. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  111. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  112. package/skills/hmos-ui-align-batch/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  113. package/skills/hmos-ui-align-batch/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  114. package/skills/hmos-ui-align-batch/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  115. package/skills/hmos-ui-align-batch/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  116. package/skills/hmos-ui-align-batch/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  117. package/skills/hmos-ui-align-batch/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  118. package/skills/hmos-ui-align-batch/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  119. package/skills/hmos-ui-align-batch/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  120. package/skills/hmos-ui-align-batch/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  121. package/skills/hmos-ui-align-batch/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  122. package/skills/hmos-ui-align-batch/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  123. package/skills/hmos-ui-align-batch/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  124. package/skills/hmos-ui-align-batch/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  125. package/skills/hmos-ui-align-batch/scripts/android_parse_fast.py +1606 -0
  126. package/skills/self-test/SKILL.md +369 -0
  127. package/skills/self-test/readme.md +309 -0
  128. package/skills/spec-generator-skill/SKILL.md +332 -0
  129. package/skills/spec-generator-skill/references/android-platform-tokens.md +105 -0
  130. package/skills/spec-generator-skill/references/spec-sample-1.md +78 -0
  131. package/skills/spec-generator-skill/references/spec-sample-2.md +58 -0
  132. package/skills/spec-generator-skill/references/spec-sample-3.md +116 -0
  133. package/skills/spec-generator-skill/references/step4-report-template.md +33 -0
  134. package/agents/self-test-setup.md +0 -165
  135. package/dist/context/resources/sdkConfig.json +0 -24
  136. package/src/context/resources/sdkConfig.json +0 -24
@@ -0,0 +1,117 @@
1
+ # Android-to-HarmonyOS UI Layout Mapping Reference
2
+
3
+ <!-- TODO: Fill in the actual UI layout mapping content -->
4
+ <!-- This reference maps Android layout containers to HarmonyOS ArkUI layout equivalents -->
5
+ <!-- Covers: LinearLayout→Column/Row, FrameLayout→Stack, ConstraintLayout→RelativeContainer, RecyclerView→List+LazyForEach, ScrollView→Scroll, etc. -->
6
+
7
+
8
+ ## 布局容器映射
9
+
10
+ | 安卓部分UI | 对应鸿蒙部分UI | 其他说明 |
11
+ |-----------|---------------|---------|
12
+ | `LinearLayout` (vertical) | `Column` 组件 | 纵向线性布局,支持 `.justifyContent()`、`.alignItems()` |
13
+ | `LinearLayout` (horizontal) | `Row` 组件 | 横向线性布局 |
14
+ | `FrameLayout` | `Stack` 组件 | 层叠布局,支持 `.alignContent()` |
15
+ | `RelativeLayout` | `RelativeContainer` 组件 | 相对布局,通过 `.alignRules()` 设置定位规则 |
16
+ | `ConstraintLayout` | `RelativeContainer` / 自定义布局 | 约束布局,复杂场景可用自定义 `onMeasureSize`/`onPlaceChildren` |
17
+ | `FlexboxLayout` | `Flex` 组件 | 弹性布局,支持 `FlexDirection`/`FlexWrap`/`FlexAlign` 标准 Flexbox |
18
+ | `GridLayout` | `Grid` 组件 | 网格布局,通过 `.columnsTemplate('1fr 1fr 1fr')` 定义列 |
19
+ | `TableLayout` | `Grid` 组件 | 表格布局,使用 `Grid` 组件模拟表格结构 |
20
+ | `CoordinatorLayout` | 组合 `Scroll` + `Column` + 动画 | 无直接对应,需组合模拟 AppBar 折叠效果 |
21
+ | `AppBarLayout` / `CollapsingToolbarLayout` | `Navigation` 标题栏 + `titleMode` | 折叠工具栏,`NavigationTitleMode.Full`→`.Mini` 自动切换 |
22
+ | `ScrollView` | `Scroll` 组件 | 滚动容器,通过 `Scroller` 控制器编程式滚动 |
23
+ | `HorizontalScrollView` | `Scroll` + `ScrollDirection.Horizontal` | 横向滚动 |
24
+ | `NestedScrollView` | `Scroll` + `.nestedScroll()` | 嵌套滚动策略配置 |
25
+ | `ViewPager` / `ViewPager2` | `Swiper` 组件 | 页面滑动切换,支持 `.autoPlay()`、`.indicator()`、`.loop()` |
26
+ | `SwipeRefreshLayout` | `Refresh` 组件 | 下拉刷新,通过 `.onRefreshing()` 回调处理刷新 |
27
+ | `RecyclerView` | `List` 组件 | 高性能列表,使用 `List` + `ForEach`/`LazyForEach` 渲染子组件 |
28
+ | `RecyclerView` (GridLayoutManager) | `Grid` 组件 | 网格布局,通过 `.columnsTemplate()` 设置列数 |
29
+ | `RecyclerView` (StaggeredGridLayoutManager) | `WaterFlow` 组件 | 瀑布流布局 |
30
+ | `ListView` | `List` 组件 | 传统列表,使用 `List` 组件实现 |
31
+ | `GridView` | `Grid` 组件 | 网格视图,使用 `Grid` 组件实现 |
32
+ | `DrawerLayout` / `NavigationView` | `SideBarContainer` 组件 | 侧边栏/抽屉 |
33
+ | `TabLayout` + `ViewPager` | `Tabs` 组件 | 标签页,`Tabs` 整合标签栏和内容页切换 |
34
+ | `BottomNavigationView` | `Tabs` + `BarPosition.End` | 底部导航栏 |
35
+ | `CardView` | `Card` 组件 / `Container` + 样式 | 卡片视图,使用 `Card` 组件或自定义样式实现 |
36
+ | `Space` | `Blank` 组件 | 空白间隔,使用 `Blank` 组件实现 |
37
+ | `FragmentContainerView` | `NavDestination` / `@Component` | 片段容器视图,映射到 `NavDestination` 或自定义组件 |
38
+ | `RecyclerViewFastScroller` | 自定义组件 | 列表快速滚动条,需自定义实现 |
39
+ | `MySearchMenu` (自定义) | `Search` 组件 + 自定义菜单 | 自定义搜索菜单,使用 `Search` 组件并添加自定义菜单 |
40
+ | `MyViewPager` (自定义) | `Swiper` 组件 | 自定义视图分页器,使用 `Swiper` 组件实现 |
41
+ | `MyRecyclerView` (自定义) | `List` 组件 | 自定义 RecyclerView,使用 `List` 组件实现 |
42
+ | `MyTextView` (自定义) | `Text` 组件 | 自定义 TextView,使用 `Text` 组件实现 |
43
+ | `MyAppCompatCheckbox` (自定义) | `Checkbox` 组件 | 自定义 Checkbox,使用 `Checkbox` 组件实现 |
44
+
45
+ ## 布局属性映射
46
+
47
+ | 安卓部分UI | 对应鸿蒙部分UI | 其他说明 |
48
+ |-----------|---------------|---------|
49
+ | `layout_weight` | `.layoutWeight()` / `.flexGrow()` | 权重分配 |
50
+ | `padding` / `margin` | `.padding()` / `.margin()` | 支持统一或分别指定 `{ top, right, bottom, left }` |
51
+ | `View.GONE` / `View.INVISIBLE` | `Visibility.None` / `Visibility.Hidden` | `None`=不占位(GONE);`Hidden`=占位不显示(INVISIBLE) |
52
+ | `android:id` | `.id('xxx')` / 组件引用变量 | 组件标识 |
53
+ | `android:background` | `.backgroundColor()` / `.backgroundImage()` | 背景 |
54
+ | `android:alpha` | `.opacity()` | 透明度 0~1 |
55
+ | `android:elevation` | `.shadow({ radius, color, offsetX, offsetY })` | 阴影 |
56
+ | `android:rotation` | `.rotate({ angle: xxx })` | 旋转 |
57
+ | `android:scaleX/Y` | `.scale({ x: xxx, y: xxx })` | 缩放 |
58
+ | `android:translationX/Y` | `.translate({ x: xxx, y: xxx })` | 平移 |
59
+ | `android:clipToOutline` | `.clip(true)` / `.clipShape()` | 裁剪 |
60
+ | `setEnabled(false)` | `.enabled(false)` | 禁用状态 |
61
+ | `android:focusable` | `.focusable(true)` | 可聚焦 |
62
+ | `android:focusableInTouchMode` | `.focusable(true)` + `.focusOnTouch(true)` | 触摸模式下可聚焦 |
63
+ | `android:clickable` | `.enabled(true)` + `.onClick()` | 可点击 |
64
+ | `android:longClickable` | `.gesture(LongPressGesture())` | 可长按 |
65
+ | `android:scrollbars` | `.scrollBar()` | 滚动条 |
66
+ | `android:fadeScrollbars` | `.scrollBar().fade(true)` | 滚动条自动隐藏 |
67
+ | `android:overScrollMode` | `.overScrollMode()` | 过度滚动模式 |
68
+ | `GradientDrawable` | `.linearGradient()` / `.radialGradient()` | 渐变背景 |
69
+ | `CardView` radius | `.borderRadius()` | 圆角 |
70
+ | `GradientDrawable.setStroke()` | `.border({ width, color, radius })` | 边框 |
71
+ | `android:layout_width` / `android:layout_height` | `.width()` / `.height()` | 宽高设置,支持具体数值、百分比、`'match_parent'`/`'wrap_content'` |
72
+ | `android:gravity` | `.alignItems()` / `.justifyContent()` | 内容对齐方式 |
73
+ | `android:layout_gravity` | `.alignSelf()` | 子组件在父容器中的对齐方式 |
74
+ | `android:orientation` (LinearLayout) | `Column` / `Row` | 布局方向 |
75
+ | `android:divider` (LinearLayout) | `Divider` 组件 | 分隔线 |
76
+ | `android:showDividers` (LinearLayout) | `Divider` 组件位置 | 分隔线显示位置 |
77
+
78
+ ## 页面结构映射
79
+
80
+ | 安卓部分UI | 对应鸿蒙部分UI | 其他说明 |
81
+ |-----------|---------------|---------|
82
+ | `Activity` | `PageAbility` | 主UI容器 |
83
+ | `Fragment` | `NavDestination` / `@Component` | 通过 Navigation + NavDestination 实现子页面路由 |
84
+ | `Service` | `ServiceAbility` | 后台服务 |
85
+ | `BroadcastReceiver` | `EventHub` + `@Event` | 广播接收器,使用事件订阅机制 |
86
+ | `ContentProvider` | `DataAbility` | 内容提供者 |
87
+ | `Toolbar` / `ActionBar` | `Navigation` 标题栏 | 通过 `.title()` 和 `.menus()` 配置 |
88
+ | `Intent` | `router` 模块 | 页面导航,使用 `router.push()`、`router.replace()` 等 |
89
+ | `Bundle` | `router.push({ params: {} })` | 页面间传递数据 |
90
+ | `onActivityResult` | `router.push()` + 回调 | 页面返回结果 |
91
+ | `startActivityForResult` | `router.push()` + 回调 | 启动页面并获取结果 |
92
+
93
+ ## 列表与可滚动组件映射
94
+
95
+ | 安卓部分UI | 对应鸿蒙部分UI | 其他说明 |
96
+ |-----------|---------------|---------|
97
+ | `RecyclerView.Adapter` | `LazyForEach` + `IDataSource` | 数据适配器,实现 `IDataSource` 接口配合 `LazyForEach` 懒加载 |
98
+ | `RecyclerView.ViewHolder` | `@Builder` / `@Component` | 声明式 UI 无需 ViewHolder 模式 |
99
+ | `LinearLayoutManager` | `List` 默认布局 | 纵向列表,`.listDirection(Axis.Horizontal)` 切换横向 |
100
+ | `GridLayoutManager` | `Grid` 组件 | 网格布局管理器 |
101
+ | `StaggeredGridLayoutManager` | `WaterFlow` 组件 | 瀑布流布局管理器 |
102
+ | `ItemTouchHelper` (swipe) | `ListItem.swipeAction()` | 列表项滑动操作菜单 |
103
+ | `ItemTouchHelper` (drag) | `ListItem.dragable()` | 列表项拖拽排序 |
104
+ | `DiffUtil` | `IDataSource` 的 `DataChangeListener` | 差异通知,`onDataAdd`/`onDataDelete`/`onDataChange` |
105
+ | `PagerAdapter` / `FragmentStateAdapter` | `Swiper` + `ForEach`/`LazyForEach` | 无需 Adapter 模式,直接循环渲染子页面 |
106
+ | `ListAdapter` | `LazyForEach` + `IDataSource` | 带 DiffUtil 的适配器 |
107
+ | `ArrayAdapter` | `ForEach` + 数组 | 数组适配器 |
108
+ | `BaseAdapter` | `ForEach` / `LazyForEach` | 基础适配器 |
109
+
110
+ ## 迁移注意事项
111
+
112
+ 1. **范式转换**:Android 使用命令式 UI(XML 布局 + Java/Kotlin 操作);ArkUI 使用声明式 UI(ArkTS 组件树 + 状态驱动渲染)
113
+ 2. **Adapter 模式消除**:`RecyclerView.Adapter`、`PagerAdapter` 等被 `ForEach`/`LazyForEach` + `IDataSource` 替代
114
+ 3. **Fragment → NavDestination**:Fragment 模块化 UI 能力映射到 `Navigation` + `NavDestination` 路由
115
+ 4. **布局属性调整**:Android 的布局属性需要转换为 ArkUI 的链式调用语法
116
+ 5. **性能优化**:使用 `LazyForEach` 进行懒加载,避免一次性渲染大量数据
117
+ 6. **响应式布局**:使用 `Flex` 和 `Grid` 组件实现响应式布局,适应不同屏幕尺寸
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ 应用页面导航工具 —— 使用 PhoneAgent 按指定路径操作设备,导航到目标页面。
5
+
6
+ 支持 Android (ADB) 和 HarmonyOS (HDC) 两种设备类型。
7
+
8
+ 依赖: pip install openpyxl phone-agent
9
+ 运行前需确保: 设备已连接 (HDC/ADB)、模型 API 可用
10
+
11
+ 用法:
12
+ # 自由 prompt 导航
13
+ python app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
14
+
15
+ # 按功能路径导航
16
+ python app_feature_verify.py --app 简单图库 --package com.simplemobiletools.gallery.pro --task "L1相册详情L2更多L3筛选媒体文件"
17
+ """
18
+
19
+ import argparse
20
+ import re
21
+ import sys
22
+ import os
23
+ from pathlib import Path
24
+
25
+ # 强制设置 UTF-8 编码以避免 Windows GBK 编码问题
26
+ if sys.platform == 'win32':
27
+ import io
28
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
29
+ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
30
+
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Excel 功能清单解析
34
+ # ---------------------------------------------------------------------------
35
+
36
+ def parse_app_feature(
37
+ file_path: str | Path | None = None,
38
+ sheet_name: str | int = 0,
39
+ ) -> list[dict]:
40
+ """解析功能清单 Excel,返回功能清单列表。"""
41
+ import openpyxl
42
+
43
+ file_path = Path(file_path)
44
+ if not file_path.exists():
45
+ raise FileNotFoundError(f"文件不存在: {file_path}")
46
+
47
+ wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True)
48
+
49
+ if isinstance(sheet_name, int):
50
+ sheet = wb.worksheets[sheet_name]
51
+ else:
52
+ sheet = wb[sheet_name]
53
+
54
+ rows = list(sheet.iter_rows(values_only=True))
55
+ wb.close()
56
+
57
+ if not rows:
58
+ return []
59
+
60
+ headers = [str(h) if h is not None else f"col_{i}" for i, h in enumerate(rows[0])]
61
+
62
+ result = []
63
+ for row in rows[1:]:
64
+ row_dict = {}
65
+ for i, val in enumerate(row):
66
+ key = headers[i] if i < len(headers) else f"col_{i}"
67
+ row_dict[key] = val
68
+ result.append(row_dict)
69
+
70
+ return result
71
+
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Markdown 功能清单解析
75
+ # ---------------------------------------------------------------------------
76
+
77
+ def parse_app_feature_md(file_path: str | Path) -> list[dict]:
78
+ """解析 Markdown 测试用例文件,返回场景列表。"""
79
+ file_path = Path(file_path)
80
+ if not file_path.exists():
81
+ raise FileNotFoundError(f"文件不存在: {file_path}")
82
+
83
+ text = file_path.read_text(encoding="utf-8")
84
+ lines = text.splitlines()
85
+
86
+ results: list[dict] = []
87
+ current_feature = ""
88
+ current_scenario: dict | None = None
89
+ collecting_sub_items: str | None = None
90
+
91
+ for line in lines:
92
+ stripped = line.strip()
93
+
94
+ feat_match = re.match(r"^##\s+Feature:\s*(.+)$", stripped)
95
+ if feat_match:
96
+ if current_scenario is not None:
97
+ results.append(current_scenario)
98
+ current_scenario = None
99
+ current_feature = feat_match.group(1).strip()
100
+ collecting_sub_items = None
101
+ continue
102
+
103
+ scen_match = re.match(r"^###\s+Scenario:\s*(.+)$", stripped)
104
+ if scen_match:
105
+ if current_scenario is not None:
106
+ results.append(current_scenario)
107
+ scenario_name = scen_match.group(1).strip()
108
+ feature_name = re.sub(r"^FEA-\d+\s*", "", current_feature)
109
+ current_scenario = {
110
+ "feature": current_feature,
111
+ "scenario": scenario_name,
112
+ "preconditions": [],
113
+ "actions": [],
114
+ "expectations": [],
115
+ "L1": feature_name,
116
+ "L2": scenario_name,
117
+ "L3": "",
118
+ }
119
+ collecting_sub_items = None
120
+ continue
121
+
122
+ if current_scenario is None:
123
+ continue
124
+
125
+ if re.match(r"^-\s*前置条件[::]", stripped):
126
+ collecting_sub_items = "preconditions"
127
+ continue
128
+
129
+ action_match = re.match(r"^-\s*动作[::]\s*(.+)$", stripped)
130
+ if action_match:
131
+ collecting_sub_items = None
132
+ current_scenario["actions"].append(action_match.group(1).strip())
133
+ continue
134
+
135
+ expect_match = re.match(r"^-\s*预期结果[::]\s*(.+)$", stripped)
136
+ if expect_match:
137
+ collecting_sub_items = None
138
+ current_scenario["expectations"].append(expect_match.group(1).strip())
139
+ continue
140
+
141
+ sub_match = re.match(r"^\s+-\s+(.+)$", line)
142
+ if sub_match and collecting_sub_items == "preconditions":
143
+ current_scenario["preconditions"].append(sub_match.group(1).strip())
144
+ continue
145
+
146
+ if stripped and not stripped.startswith("-"):
147
+ collecting_sub_items = None
148
+
149
+ if current_scenario is not None:
150
+ results.append(current_scenario)
151
+
152
+ return results
153
+
154
+
155
+ # ---------------------------------------------------------------------------
156
+ # 应用注册
157
+ # ---------------------------------------------------------------------------
158
+
159
+ def register_app(app_name: str, package: str, device_type: str, ability: str = "EntryAbility") -> None:
160
+ """将应用注册到 phone-agent 的配置中。"""
161
+ if device_type == "hdc":
162
+ from phone_agent.config import apps_harmonyos
163
+ apps_harmonyos.APP_PACKAGES[app_name] = package
164
+ apps_harmonyos.APP_ABILITIES[package] = ability
165
+ else:
166
+ from phone_agent.config import apps
167
+ apps.APP_PACKAGES[app_name] = package
168
+
169
+
170
+ # ---------------------------------------------------------------------------
171
+ # 功能路径提取
172
+ # ---------------------------------------------------------------------------
173
+
174
+ def _get_feature_path(item: dict) -> tuple[str, str]:
175
+ """从功能项提取层级路径和叶子节点名称。"""
176
+ keys = [k for k in ("L1", "L2", "L3") if k in item]
177
+ if not keys:
178
+ keys = [k for k in ("一级", "二级", "三级") if k in item]
179
+ if not keys:
180
+ keys = list(item.keys())[:3]
181
+
182
+ parts = []
183
+ for k in keys:
184
+ v = item.get(k)
185
+ if v is not None and str(v).strip():
186
+ parts.append(str(v).strip())
187
+
188
+ if not parts:
189
+ return "", ""
190
+ return " > ".join(parts), parts[-1]
191
+
192
+
193
+ # ---------------------------------------------------------------------------
194
+ # 构建导航 prompt
195
+ # ---------------------------------------------------------------------------
196
+
197
+ def _build_navigate_prompt(app_name: str, path_str: str, leaf: str, app_hints: str | None = None) -> str:
198
+ """构建导航到目标页面的 prompt。"""
199
+ prompt = (
200
+ f"打开{app_name},按照以下路径依次点击导航到目标页面:{path_str}。"
201
+ f"最终目标是到达「{leaf}」所在的页面。"
202
+ "注意:"
203
+ "1. 鸿蒙手机浅色/深色模式切换路径:设置->显示与亮度"
204
+ "2. '更多'组件可能表示为 三横 或者 三点。"
205
+ "3. '设置'组件可能表示为 齿轮。"
206
+ "到达目标页面后请回复'已到达',无法到达请回复'未找到'。"
207
+ )
208
+ if app_hints:
209
+ prompt += f"\n补充提示: {app_hints}"
210
+ return prompt
211
+
212
+
213
+ # ---------------------------------------------------------------------------
214
+ # 判断是否到达目标页面
215
+ # ---------------------------------------------------------------------------
216
+
217
+ def _judge_reached(msg: str) -> bool:
218
+ """根据 agent 回复判断是否到达了目标页面。"""
219
+ if not msg:
220
+ return False
221
+ reached_keywords = ("已到达", "到达", "存在", "找到", "完成", "成功", "已确认", "已进入", "已打开")
222
+ failed_keywords = ("未找到", "不存在", "无法找到", "无法到达", "没有", "失败", "未到达")
223
+ first_line = msg.split('\n')[0]
224
+ if any(kw in first_line for kw in failed_keywords):
225
+ return False
226
+ if any(kw in first_line for kw in reached_keywords):
227
+ return True
228
+ return False
229
+
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # phone-agent runtime patches (idempotent)
233
+ # ---------------------------------------------------------------------------
234
+
235
+ def _apply_phone_agent_patches():
236
+ """Monkey-patch phone_agent bugs so the skill works on an unpatched install."""
237
+ if getattr(_apply_phone_agent_patches, '_done', False):
238
+ return
239
+ _apply_phone_agent_patches._done = True
240
+
241
+ import math
242
+ import time as _time
243
+
244
+ # Patch 1: HarmonyOS swipe — last arg should be velocity (pps), not duration (ms)
245
+ import phone_agent.hdc.device as _hdc
246
+ if not getattr(_hdc.swipe, '_patched', False):
247
+ def _swipe(start_x, start_y, end_x, end_y,
248
+ duration_ms=None, device_id=None, delay=None):
249
+ if delay is None:
250
+ delay = _hdc.TIMING_CONFIG.device.default_swipe_delay
251
+ hdc_prefix = _hdc._get_hdc_prefix(device_id)
252
+ distance = math.sqrt((start_x - end_x) ** 2 + (start_y - end_y) ** 2)
253
+ if duration_ms is None:
254
+ duration_ms = int(distance ** 2 / 1000)
255
+ duration_ms = max(500, min(duration_ms, 1000))
256
+ velocity = int((distance / duration_ms) * 4000)
257
+ velocity = max(500, min(velocity, 40000))
258
+ _hdc._run_hdc_command(
259
+ hdc_prefix + [
260
+ "shell", "uitest", "uiInput", "swipe",
261
+ str(start_x), str(start_y), str(end_x), str(end_y),
262
+ str(velocity),
263
+ ],
264
+ capture_output=True,
265
+ )
266
+ _time.sleep(delay)
267
+ _swipe._patched = True
268
+ _hdc.swipe = _swipe
269
+
270
+ # Patch 2: parse_action — regex fallback when do() contains unescaped quotes
271
+ import phone_agent.actions.handler as _handler
272
+ if not getattr(_handler.parse_action, '_patched', False):
273
+ _orig_parse = _handler.parse_action
274
+ def _parse_action(response):
275
+ try:
276
+ return _orig_parse(response)
277
+ except ValueError as e:
278
+ if 'Failed to parse do() action' in str(e):
279
+ m = re.search(
280
+ r'do\s*\(\s*action\s*=\s*"([^"]*)"\s*,\s*message\s*="(.*)"\s*\)\s*$',
281
+ response.strip(), re.DOTALL,
282
+ )
283
+ if m:
284
+ msg = m.group(2)
285
+ msg = msg.replace("\\n", "\x0a").replace("\\t", "\x09").replace("\\r", "\x0d")
286
+ msg = msg.replace('\\"', '"')
287
+ return {"_metadata": "do", "action": m.group(1), "message": msg}
288
+ raise
289
+ _parse_action._patched = True
290
+ _handler.parse_action = _parse_action
291
+
292
+
293
+ # ---------------------------------------------------------------------------
294
+ # 核心导航逻辑
295
+ # ---------------------------------------------------------------------------
296
+
297
+ def navigate(
298
+ model_config,
299
+ app_name: str,
300
+ package: str,
301
+ device_type: str = "hdc",
302
+ prompt: str | None = None,
303
+ single_task: str | None = None,
304
+ feature_file: str | Path = "",
305
+ sheet_name: int = 0,
306
+ offset: int = 1,
307
+ limit: int | None = None,
308
+ max_steps: int = 30,
309
+ verbose: bool = True,
310
+ app_hints: str | None = None,
311
+ ) -> None:
312
+ """按指定路径操作设备导航到目标页面,输出是否到达。"""
313
+ _apply_phone_agent_patches()
314
+ register_app(app_name, package, device_type)
315
+
316
+ from phone_agent import PhoneAgent
317
+ from phone_agent.agent import AgentConfig
318
+ from phone_agent.device_factory import set_device_type, DeviceType
319
+
320
+ agent_config = AgentConfig(max_steps=max_steps, verbose=verbose)
321
+ set_device_type(DeviceType.HDC if device_type == "hdc" else DeviceType.ADB)
322
+ agent = PhoneAgent(model_config=model_config, agent_config=agent_config)
323
+
324
+ # 收集要导航的目标列表: [(prompt_text, display_label), ...]
325
+ tasks: list[tuple[str, str]] = []
326
+
327
+ if prompt:
328
+ full_prompt = f"打开{app_name},{prompt}"
329
+ tasks.append((full_prompt, prompt))
330
+ elif single_task:
331
+ level_parts = re.split(r'(L[123])', single_task)
332
+ item = {}
333
+ for j in range(1, len(level_parts) - 1, 2):
334
+ key = level_parts[j]
335
+ val = level_parts[j + 1].strip()
336
+ if val:
337
+ item[key] = val
338
+ path_str, leaf = _get_feature_path(item)
339
+ if path_str:
340
+ tasks.append((_build_navigate_prompt(app_name, path_str, leaf, app_hints), path_str))
341
+ else:
342
+ feature_path = Path(feature_file)
343
+ if feature_path.suffix.lower() == ".md":
344
+ features = parse_app_feature_md(file_path=feature_file)
345
+ else:
346
+ features = parse_app_feature(file_path=feature_file, sheet_name=sheet_name)
347
+ if offset > 1:
348
+ features = features[offset - 1:]
349
+ if limit is not None:
350
+ features = features[:limit]
351
+ for item in features:
352
+ path_str, leaf = _get_feature_path(item)
353
+ if path_str:
354
+ tasks.append((_build_navigate_prompt(app_name, path_str, leaf, app_hints), path_str))
355
+
356
+ for i, (task_prompt, label) in enumerate(tasks):
357
+ if len(tasks) > 1:
358
+ print(f"\n[{i + 1}/{len(tasks)}] 导航: {label}")
359
+ else:
360
+ print(f"导航: {label}")
361
+
362
+ try:
363
+ result_msg = agent.run(task_prompt)
364
+ reached = _judge_reached(result_msg)
365
+ except Exception:
366
+ reached = False
367
+
368
+ if reached:
369
+ print("到达目标页面")
370
+ else:
371
+ print("未找到目标页面")
372
+
373
+ if i < len(tasks) - 1:
374
+ agent.reset()
375
+
376
+
377
+ # ---------------------------------------------------------------------------
378
+ # CLI 入口
379
+ # ---------------------------------------------------------------------------
380
+
381
+ def main():
382
+ parser = argparse.ArgumentParser(
383
+ description="按指定路径操作设备导航到目标页面(支持 Android / HarmonyOS)"
384
+ )
385
+ parser.add_argument("--device", choices=["hdc", "adb"], default="hdc",
386
+ help="设备类型: hdc=HarmonyOS, adb=Android(默认 hdc)")
387
+ parser.add_argument("--app", type=str, required=True, help="应用名称")
388
+ parser.add_argument("--package", type=str, required=True, help="应用包名")
389
+ parser.add_argument("--task", type=str, default=None,
390
+ help="功能路径,格式: 'L1xxxL2xxxL3xxx'")
391
+ parser.add_argument("--prompt", type=str, default=None,
392
+ help="直接注入给 Agent 的自然语言导航指令")
393
+ parser.add_argument("--feature", type=str, default=None,
394
+ help="功能清单文件路径(.xlsx / .md)")
395
+ parser.add_argument("--limit", type=int, default=None, help="仅导航前 N 条")
396
+ parser.add_argument("--offset", type=int, default=1, help="从第几条开始(1-based)")
397
+ parser.add_argument("--sheet", type=int, default=0, help="Excel 工作表索引")
398
+ parser.add_argument("--api-key", type=str, default="", help="智谱 API Key")
399
+ parser.add_argument("--max-steps", type=int, default=30, help="Agent 最大步数")
400
+ parser.add_argument("--app-hints", type=str, default=None, help="应用上下文提示")
401
+ parser.add_argument("-q", "--quiet", action="store_true", help="减少输出")
402
+ args = parser.parse_args()
403
+
404
+ if not args.prompt and not args.task and not args.feature:
405
+ parser.error("必须指定 --feature、--task 或 --prompt 之一")
406
+
407
+ api_key = args.api_key or os.environ.get("GLM_API_KEY", "")
408
+ if not api_key and not args.quiet:
409
+ print("提示: 未设置 API Key,请通过 --api-key 或环境变量 GLM_API_KEY 配置")
410
+ print("继续运行可能因鉴权失败而报错\n")
411
+
412
+ from phone_agent.model import ModelConfig
413
+
414
+ model_config = ModelConfig(
415
+ base_url="https://open.bigmodel.cn/api/paas/v4",
416
+ api_key=api_key,
417
+ model_name="autoglm-phone",
418
+ temperature=0.1,
419
+ )
420
+
421
+ device_label = "ADB (Android)" if args.device == "adb" else "HDC (HarmonyOS)"
422
+ print(f"设备: {device_label}, 应用: {args.app} ({args.package})")
423
+ print("-" * 50)
424
+
425
+ navigate(
426
+ model_config=model_config,
427
+ app_name=args.app,
428
+ package=args.package,
429
+ device_type=args.device,
430
+ prompt=args.prompt,
431
+ single_task=args.task,
432
+ feature_file=args.feature or "",
433
+ sheet_name=args.sheet,
434
+ offset=args.offset,
435
+ limit=args.limit,
436
+ max_steps=args.max_steps,
437
+ verbose=not args.quiet,
438
+ app_hints=args.app_hints,
439
+ )
440
+
441
+
442
+ if __name__ == "__main__":
443
+ sys.exit(main())
@@ -0,0 +1,37 @@
1
+ Create a timestamped output directory under `capture_output_dir`:
2
+ ```
3
+ task_dir = capture_output_dir / "task_{timestamp}"
4
+ android_capture_dir = task_dir / "android_page_{i}_{name}"
5
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
6
+ ```
7
+ Each paired hmos-android page-pair should have the same `i`
8
+
9
+ ## Navigation prompt rules
10
+
11
+ Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
12
+ The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
13
+
14
+ ## Navigate to Android page and Capture (If navigated Success)
15
+ ```bash
16
+ PYTHONIOENCODING=utf-8 python ./app_feature_verify.py \
17
+ --device adb \
18
+ --app "{android.app_name}" \
19
+ --package "{android.package}" \
20
+ --prompt "{pages[i].android_nav_path}" \
21
+ --api-key "{glm_api_key}" \
22
+ --max-steps 15
23
+ ```
24
+
25
+ **Capture Android page** (if navigation succeeded)
26
+ ```bash
27
+ PYTHONIOENCODING=utf-8 python ./page_capture.py --device adb -o "{android_capture_dir}"
28
+ ```
29
+
30
+ ## Navigate & Capture HarmonyOS Pages
31
+
32
+ Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
33
+ ```
34
+ hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
35
+ ```
36
+ For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
37
+