diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..02d88b0 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "ignores": [ + "docs/dev/UNFINISHED_FEATURES.md", + "docs/api/doc/API_DOC.md" + ] +} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/Index.ets b/.windsurf/workflows/tools.md similarity index 100% rename from packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/Index.ets rename to .windsurf/workflows/tools.md diff --git a/AGENTS.md b/AGENTS.md index d8f78ff..e2570ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ - +# AGENTS.md 优先使用ios风格的组件,若Cupertino无对应组件 再使用material 每个文件头部需要增加标准注释,创建时间 更新时间 名称 作用 上次更新内容,代码部分 分类和方法也需要注释 @@ -19,16 +19,17 @@ api接口部分,可在本地使用接口请求验证,确保接口正常响 关于 CHANGELOG.md 每次对代码修改,功能的增删必须写日志记录和功能变更,变化大的需修改版本号, CHANGELOG.md 里面必须包含说明文档,不可删除CHANGELOG.md - CHANGELOG.md的内容需确保更换其他ai coder后也能看懂当前项目, +CHANGELOG.md的内容需确保更换其他ai coder后也能看懂当前项目, CHANGELOG.md仅保留5个版本号信息,去除较早的版本号, 已去除的版本号写入软件特性功能,已开发完成或开发中在开发进度中, 若多次提到的功能需提升优先级,优先级值1-5。 - 每个文件尽量不要超过1000行代码,低于200行代码的文件尽量和其他文件合并 要求符合ios26 风格ui ,使用主题色 主题背景 主题字体 主题样式 多语言等 ## 纲领约束 + - 暂不开发注册登录功能(优先级最低,当前阶段不涉及用户认证体系) - 暂不开消息通知功能,(后续可能使用邮箱实现) - + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 791a008..3b2b462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,212 +2,543 @@ All notable changes to this project will be documented in this file. -## [0.67.0] - 2026-04-11 +## [0.88.1] - 2026-04-11 -### Fixed — 阶段二十:数据模型类型安全增强 +### Enhanced — 软件特性功能完善 -- 🐛 **20.1 categoryHierarchy 解析异常** — `recipe_model.dart` - - `_parseCategoryHierarchy` 添加 try-catch 包裹,防止解析失败导致崩溃 - - `CategoryHierarchyItem.fromJson` 使用安全类型转换 `_safeInt`/`_safeString` - - `RecipeAuthor.fromJson` 同步增强,统一使用安全解析方法 - - 解决 API 返回字段为 null 或类型不匹配时的运行时异常 +- ✨ **功能状态审核与完善** + - **购物清单** - ✅ 已完成:菜谱详情页"购物"按钮可添加食材到购物清单 + - **过敏原检测** - ✅ 已完成:AllergenChecker完整实现,包含11类过敏原关键词映射和检测逻辑 + - **烹饪笔记** - ✅ 已完成:CookingNotePage完整实现,支持按菜谱关联笔记 + - **份量缩放** - ✅ 已完成:菜谱详情页"缩放"按钮可传递食材到serving_scaler_page + - **食材详情查询** - ✅ 已完成:包含营养信息、选购技巧、存储提示、关键营养素、最佳时令等 + - **每周菜单规划** - ✅ 已完成:七日横向滑动日历、三餐分配、购物清单、Hive持久化 + - **热量追踪+营养分析** - ✅ 已完成:包含环形图、柱状图、饼图(营养素占比+餐次分布)、折线图(热量趋势) + - **AI菜谱推荐** - ✅ 已完成:基于用户偏好、浏览历史、收藏记录的智能推荐 + - **就寝提醒** - ✅ 已完成:智能推荐就寝时间、睡前进食提醒、健康小贴士 -## [0.66.0] - 2026-04-11 +- 📝 **文档更新** + - 更新`UNFINISHED_FEATURES.md`软件特性功能汇总表 + - 修正不实标记,准确反映功能完成状态 + - 新增v0.88.x功能条目 -### Fixed — 阶段十九综合Bug修复+功能增强 +--- -- 🐛 **19.1 发现页更多按钮卡死** — `tools_center_page.dart` - - 添加ToolsController安全检查,防止未注册时崩溃 +## [0.88.0] - 2026-04-11 -- 🐛 **19.2 烹饪计时器常用预设** — `cooking_timer_page.dart` - - 添加18种常用烹饪步骤快捷添加(煮鸡蛋/煮面条/炖汤等) +### Added — 阶段十三:AI+规划高级功能 -- 🐛 **19.3 菜谱详情显示全部数据** — `recipe_detail_page.dart` - - 显示浏览量/营养成分/分类/标签/过敏原/时间等全部字段 - - 笔记icon改为横向滚动,修复溢出 +- ✨ **13.1 AI菜谱推荐** — `lib/src/services/recommendation_service.dart`, `lib/src/pages/home/home_page.dart` + - **功能**:基于用户偏好、浏览历史、收藏记录的智能菜谱推荐 + - **实现**: + - 创建RecommendationService推荐服务 + - 基于PreferenceController的用户偏好(分类、标签、过敏原) + - 基于FavoritesController的收藏记录 + - 基于浏览历史记录 + - 实现个性化推荐算法(协同过滤+热度加权) + - 支持相似菜谱推荐 + - **UI更新**: + - 首页添加"为你推荐"Tab + - Tab切换:今日推荐/为你推荐 + - 推荐结果横向滑动卡片展示 + - **影响文件**: + - `lib/src/services/recommendation_service.dart` (新建) + - `lib/src/pages/home/home_page.dart` (修改) -- 🐛 **19.4 口味偏好分类+标签修复** — `user_preference_model.dart` - - 修复PreferenceCategory id字符串解析+children子分类+标签显示 +- ✨ **13.2 每周菜单规划** — `lib/src/pages/tools/weekly_menu_planner_page.dart` + - **功能**:日历视图规划一周饮食,自动生成购物清单 + - **实现**: + - 创建WeeklyMenuModel数据模型 + - 创建WeeklyMenuController控制器 + - 七日横向滑动日历视图 + - 每日早/中/晚三餐分配菜谱 + - 从菜谱列表选择添加到对应餐次 + - 自动汇总生成购物清单 + - 支持食材勾选状态 + - Hive持久化存储菜单数据 + - **UI设计**: + - iOS风格日历组件 + - 餐次卡片展示(早餐/午餐/晚餐) + - 购物清单弹窗 + - 菜谱选择器 + - **影响文件**: + - `lib/src/models/weekly_menu_model.dart` (新建) + - `lib/src/controllers/weekly_menu_controller.dart` (新建) + - `lib/src/pages/tools/weekly_menu_planner_page.dart` (新建) -- 🐛 **19.5 热门排行数据修复** — `hot_repository.dart` - - 添加fallback机制,period无数据时回退total +- ✨ **13.3 食材用量换算增强** — `lib/src/pages/tools/serving_scaler_page.dart` + - **功能**:支持多种单位换算(已在之前版本实现) + - **实现**: + - 重量单位:克/千克/磅/盎司/斤/两 + - 容量单位:毫升/升/杯/汤匙/茶匙/fl oz + - 计数单位:个/根/片/瓣/条/块/把/勺/滴 + - 常用换算快捷参考表 + - **UI更新**: + - 添加单位换算Tab + - 分类选择器(重量/容量/计数) + - 单位选择器 + - 换算结果展示 + - **影响文件**: + - `lib/src/pages/tools/serving_scaler_page.dart` (已实现,无需修改) -- 🐛 **19.6 购物清单按钮增大** — `shopping_list_page.dart` - - 勾选/删除按钮增大至44x44点击区域 +- ✨ **13.4 就寝提醒** — `lib/src/pages/profile/bedtime_reminder_page.dart` + - **功能**:根据晚餐时间推荐健康作息 + - **实现**: + - 创建BedtimeReminderController控制器 + - 晚餐时间选择器 + - 智能推荐就寝时间(晚餐后3小时) + - 睡前进食提醒(睡前2小时内不宜进食) + - 提醒设置开关 + - 提前提醒设置(15-60分钟) + - Hive持久化存储设置 + - 健康小贴士展示 + - **UI设计**: + - 晚餐时间卡片 + - 推荐就寝时间展示 + - 提醒设置卡片 + - 睡前进食提醒卡片(动态警告) + - 健康小贴士卡片 + - **影响文件**: + - `lib/src/controllers/bedtime_reminder_controller.dart` (新建) + - `lib/src/pages/profile/bedtime_reminder_page.dart` (新建) -- 🐛 **19.7 我的页面左右滑动** — `profile_page.dart` - - 使用PageView替代条件渲染,支持手势滑动 +### Changed — 路由配置更新 -- 🐛 **19.8 笔记保存后不显示** — `cooking_note_page.dart` - - 修复异步保存未await+添加Obx响应式刷新 +- 🔄 **app_routes.dart** — 添加新页面路由 + - 添加 `weeklyMenuPlanner` 路由 + - 添加 `bedtimeReminder` 路由 + - 注册对应的GetPage + - **影响文件**:`lib/src/config/app_routes.dart` -- 🐛 **19.9 深色模式跟随系统** — `theme_service.dart` - - 添加DarkModeSource枚举,支持system/manual模式 +--- -- 🐛 **19.10 字体大小全局生效** — `main.dart` - - 通过GetCupertinoApp.builder设置全局textScaleFactor +## [0.87.0] - 2026-04-11 -- 🐛 **19.11 底部Tab栏高度+安全区** — `glass_nav_bar.dart` - - 高度增加,添加底部安全区域padding +### Fixed — 阶段三十九:Android Release 包白屏问题修复 -- 🐛 **19.12 白色区域遮住底部** — `navigation_widgets.dart` - - 移除SafeArea(bottom:false),GlassNavBar自行处理安全区域 +- 🐛 **39.1 Android Release 包安装后白屏** — `android/app/build.gradle.kts`, `android/app/src/main/AndroidManifest.xml` + - **问题**:安卓端打包 release 安装后打开白屏,debug 包正常 + - **根因分析**: + - 应用使用 HTTP 协议(`http://eat.wktyl.com`),Android 9+ 默认禁止明文 HTTP 流量 + - Release 构建缺少 ProGuard 混淆规则,导致 Flutter 引擎相关类被错误混淆 + - **修复方案**: + - ✅ 创建 `proguard-rules.pro` 文件,添加 Flutter 引擎和插件混淆规则 + - ✅ 创建 `network_security_config.xml`,允许 `eat.wktyl.com` 域名使用 HTTP 明文流量 + - ✅ 修改 `build.gradle.kts`,在 release 构建中启用代码混淆和资源压缩 + - ✅ 修改 `AndroidManifest.xml`,添加网络安全配置引用 + - **影响文件**: + - `android/app/proguard-rules.pro` (新建) + - `android/app/src/main/res/xml/network_security_config.xml` (新建) + - `android/app/build.gradle.kts` + - `android/app/src/main/AndroidManifest.xml` -- ✨ **19.13 推荐分类层级导航** — `category_browse_page.dart` - - 大类→小类→菜谱列表→详情,新建CategoryBrowsePage +--- -- 🐛 **19.14 今天吃什么GetX报错** — `what_to_eat_page.dart` - - 控制器注册+加载优化+分类扁平化 +## [0.86.0] - 2026-04-11 -- ✨ **19.15 搜索显示相似结果** — `search_page.dart` + `search_controller.dart` - - 无结果时提取关键词模糊搜索,显示相似推荐列表 +### Fixed — 阶段三十八:多项问题修复与功能优化 -- ⚙️ **19.16 设置功能全局生效** — `navigation_widgets.dart` - - 底部栏样式已全局生效(贴边/悬浮切换) +- 🐛 **38.1 搜索结果点击卡死闪退** — `recipe_repository.dart`, `recipe_model.dart` + - **问题**:点击搜索结果跳转到详情页时,应用卡死闪退 + - **根因分析**: + - API 返回的 JSON 数据类型为 `Map` + - 代码中使用 `as Map` 强制类型转换失败 + - **修复方案**: + - ✅ 添加 `_safeMap` 和 `_safeMapOrNull` 辅助方法 + - ✅ 修复 `fetchFull`, `search`, `fetchIngredients` 等方法中的类型转换 + - ✅ 修复 `_parseStatistics`, `_parseMeta`, `_parseNutrition` 等解析方法 + - ✅ 修复 `NutritionInfo.fromList` 中的类型过滤问题 + - **影响文件**:`recipe_repository.dart`, `recipe_model.dart` -- ✨ **19.17 用餐时段推荐页** — `eating_times_page.dart` - - 基于eating_times.json创建5类时段浏览+菜谱列表 +- 🐛 **38.2 菜品详情页笔记功能卡死闪退** — `cooking_note_page.dart` + - **问题**:点击笔记按钮跳转到笔记页面时,应用卡死闪退 + - **根因分析**: + - 页面中控制器获取逻辑过于复杂 + - 存在未使用的 `_buildErrorView` 和 `_buildErrorBody` 方法 + - **修复方案**: + - ✅ 简化控制器获取逻辑,直接使用 `Get.find()` + - ✅ 移除冗余的错误处理代码 + - ✅ 统一使用 `_controller` 变量 + - **影响文件**:`cooking_note_page.dart` -- ⚡ **19.18 网络请求优化** — `api_service.dart` - - 增强日志拦截器+重试机制+统一离线检查+缓存解析修复 +### Added — 阶段三十八:新功能 -## [0.65.0] - 2026-04-11 +- ✨ **38.3 主页顶部按时段问候语** — `home_page.dart` + - **功能**:主页顶部右侧显示按时段变化的问候语 + - **交互**:点击问候语切换显示时段/提示语,循环切换 + - **时段划分**: + - 清晨 (5-7时): "清晨好" / "清晨了,呼吸新鲜空气" + - 早上 (7-9时): "早上好" / "早上好,元气满满" + - 上午 (9-12时): "上午好" / "上午好,努力工作" + - 中午 (12-14时): "中午好" / "中午了,吃顿好的" + - 下午 (14-17时): "下午好" / "下午了,喝杯咖啡" + - 傍晚 (17-19时): "傍晚好" / "傍晚了,休息一下" + - 晚上 (19-22时): "晚上好" / "晚上好,放松身心" + - 深夜 (22-5时): "夜深了" / "深夜了,注意身体" + - **影响文件**:`home_page.dart` -### Added — 阶段任务补全(12/13/14/16) +--- -- ✨ **12.1 分享菜谱** — `recipe_detail_page.dart` - - 菜谱详情页添加分享按钮(CupertinoIcons.share) - - 生成格式化分享文本:菜名+食材列表+做法+来源标识 - - 调用 CommonUtils.shareContent(底层 share_plus) +## [0.85.0] - 2026-04-11 -- ✨ **12.3 搜索热词从API获取** — `search_controller.dart` - - 热门搜索词改为从 RecipeRepository.fetchTags() API 获取 - - API 获取失败时保留硬编码 fallback 热词(10个经典菜名) - - onInit 时自动加载热词 +### Fixed — 阶段三十七:Assets 路径配置错误修复 -- ✨ **13.3 食材用量换算增强** — `serving_scaler_page.dart` - - 添加 CupertinoSegmentedControl 切换「份量缩放」/「单位换算」Tab - - 单位换算支持三大类:⚖️重量(g/kg/lb/oz/斤/两)、🥛容量(ml/L/杯/汤匙/茶匙)、🔢计数(个/根/片/瓣/条/块/把/勺/滴) - - 实时计算换算结果,支持任意单位互转 - - 底部展示常用换算速查表(8组常用换算) +- 🐛 **37.1 Assets 目录路径配置错误** — `pubspec.yaml`, `recipe_image.dart` + - **问题**:运行时报错 `Error: unable to find directory entry in pubspec.yaml: assets/data/photos/` + - **根因分析**: + - 实际目录结构是 `assets/photos/error.png` + - pubspec.yaml 配置的是 `assets/data/photos/` + - recipe_image.dart 中引用的路径也是 `assets/data/photos/error.png` + - **修复方案**: + - ✅ 修正 pubspec.yaml 中的 assets 配置为 `assets/photos/` + - ✅ 修正 recipe_image.dart 中的路径为 `assets/photos/error.png` + - **影响文件**:`pubspec.yaml`, `recipe_image.dart` -- ✨ **14.3 过敏原警示增强** — `allergen_checker.dart` + `recipe_detail_page.dart` - - AllergenChecker 添加食材替代建议映射(11类过敏原→替代食材) - - 菜谱详情页过敏原警示区展示替代建议(🔄 标识) +--- -- ✨ **14.4 点赞/推荐系统完善** — `recipe_detail_page.dart` - - 点赞按钮显示当前状态(实心/空心心形+颜色变化) - - 推荐按钮显示当前推荐状态 - - 五星评分对话框(1-5星+表情描述) +## [0.84.0] - 2026-04-11 -- ✨ **14.8 浏览量统计+热度标签** — `recipe_detail_page.dart` - - 菜谱详情页展示浏览次数+热度标签(🔥热门/🔥🔥非常热门) +### Fixed — 阶段三十六:图片加载验证逻辑优化 -- ✨ **16.6 收藏页面UI重构** — `favorites_page.dart` - - 全面重构为 iOS 26 Liquid Glass 风格 - - 所有卡片/按钮/筛选器使用 BackdropFilter + 半透明背景 - - 统一使用 DarkDesignTokens.glass / glassBorder 设计令牌 - - 空状态居中毛玻璃卡片,编辑栏底部毛玻璃效果 - - 收藏项卡片毛玻璃+细边框,选中态半透明高亮 +- 🐛 **36.1 图片全部显示error.png,网络图片无法加载** — `recipe_image.dart` + - **问题**:服务器上 `{picId}a.jpg` 图片存在,但所有图片都显示本地 `error.png` + - **根因分析**: + - `_canDecodeImage` 方法中的 `buffer.dispose()` 在 `codec.dispose()` 之后调用 + - 在某些情况下,这可能导致解码验证失败 + - 解码验证过于严格,导致有效图片被拒绝 + - **修复方案**: + - ✅ 移除 `_canDecodeImage` 解码验证方法 + - ✅ 仅保留 `_isValidImageData` 头部校验(检查 JPEG/PNG/GIF/WebP/BMP 魔数) + - ✅ 添加详细调试日志,便于追踪加载流程 + - ✅ 更新 fallback 链:`coverUrl → {picId}a.jpg → {picId}b.jpg → {picId}.jpg → 本地error.png → 空白` + - **影响文件**:`recipe_image.dart` -## [0.64.0] - 2026-04-11 +--- -### Added — 阶段十八:浑水摸鱼功能补全 +## [0.83.0] - 2026-04-11 -- ✨ **18.1 烹饪笔记页面** — `cooking_note_page.dart` - - 创建完整CookingNotePage,支持按菜谱关联的笔记增删改查 - - 从菜谱详情页跳转时携带recipeId和recipeTitle +### Fixed — 阶段三十五:图片加载逻辑优化 -- ✨ **18.2 过敏原检测实现** — `allergen_checker.dart` - - 接入PreferenceController获取用户过敏原偏好设置 - - 实现11类过敏原关键词匹配检测(坚果/海鲜/乳制品/蛋类/谷物/豆类/肉类/水果/蔬菜/菌类/调味品) - - checkAllergens返回实际检测结果,isAllergen正确判断 +- 🐛 **35.1 图片显示不正确,全部显示back.png** — `recipe_image.dart` + - **问题**:所有图片都显示为远程 `back.png`,而非正确的菜谱图片 + - **根因分析**: + - 图片加载 fallback 链中包含远程 `back.png` 作为回退 + - 导致所有图片最终都回退到 `back.png` + - **修复方案**: + - ✅ 移除远程 `back.png` 回退 + - ✅ 更新 fallback 链:`coverUrl → {picId}a.jpg → {picId}b.jpg → {picId}.jpg → 本地error.png → 空白` + - ✅ 直接使用本地 `error.png` 作为最终回退 + - **影响文件**:`recipe_image.dart` -- ✨ **18.3 份量缩放从菜谱导入** — `serving_scaler_page.dart` - - ServingScalerPage支持ingredients和defaultServings参数 - - 从菜谱详情页跳转时携带真实食材列表,替代硬编码数据 - - 菜谱详情页添加"份量缩放"按钮 +--- -- ✨ **18.4 菜单规划数据持久化** — `meal_planner_page.dart` - - 接入StorageService+SharedPreferences按周保存/加载菜单数据 - - 自动检测周次变化,清理过期数据 +## [0.82.0] - 2026-04-11 -- ✨ **18.5 菜单规划从收藏添加** — `meal_planner_page.dart` - - 实现_addFromFavorites方法,CupertinoActionSheet选择收藏菜谱 - - 从FavoritesController获取收藏列表数据 +### Fixed — 阶段三十四:搜索结果点击卡死闪退修复 -- ✅ **18.6 购物清单从菜谱添加** — 已确认实现 - - recipe_detail_page.dart已有_addToShoppingList方法和"购物"按钮 +- 🐛 **34.1 搜索结果点击导致应用崩溃** — `recipe_model.dart`, `feed_item_model.dart`, `api_response.dart`, `ingredient_model.dart` + - **问题**:点击搜索结果跳转到详情页时,应用卡死闪退 + - **根因分析**: + - `RecipeModel.fromJson` 等模型解析方法中使用 `as Map` 强制类型转换 + - 当 API 返回的数据类型为 `Map` 时,强制转换失败抛出异常 + - 异常未被捕获,导致应用崩溃 + - **修复方案**: + - ✅ 将 `as Map` 替换为安全的类型检查 `is Map` + - ✅ 添加 `Map.from(v)` 作为降级处理 + - ✅ 使用 try-catch 包裹解析逻辑,防止异常传播 + - ✅ 使用 `whereType()` 过滤掉解析失败的项 + - **影响文件**: + - `recipe_model.dart` - 修复 `_parseIngredients`, `_parseList`, `_parseIngredientDetail` + - `feed_item_model.dart` - 修复 `_parseStatistics` + - `api_response.dart` - 修复 `PaginatedData.fromJson` + - `ingredient_model.dart` - 修复 `_parseStatistics` -- ✨ **18.7 食材详情营养信息** — `ingredient_detail_page.dart` + `ingredient_nutrition_db.dart` - - 创建IngredientNutritionDb营养数据库(60+种常见食材) - - 页面展示:热量大字+营养概览(热量/蛋白质/脂肪/碳水/纤维)+营养素占比条+关键营养素标签+时令季节+选购技巧+储存方法 - - 列表卡片显示热量预览信息 - - 支持模糊匹配和分类回退 +--- -- ✨ **18.8 营养中心饼图+折线图** — `charts_widgets.dart` + `nutrition_report_page.dart` - - 新增MealTypePieChart组件(早/午/晚/加餐热量分布饼图) - - 营养报告页集成餐次分布饼图卡片 - - 现有图表完整:折线图(热量趋势)+营养素饼图+餐次分布饼图+进度条 +## [0.81.0] - 2026-04-11 -### 新增文件 -- `lib/src/services/data/ingredient_nutrition_db.dart` — 食材营养数据库 +### Fixed — 阶段三十三:多项UI与数据问题修复 -## [0.63.0] - 2026-04-11 +- 🐛 **33.1 主页搜索框顶部多余视图移除** — `home_page.dart` + - **问题**:主页标题和搜索框之间存在多余的视图 + - **修复**:移除重复的搜索按钮组件,清理冗余代码 + - **影响文件**:`home_page.dart` -### Fixed — 控制器注册重复与生命周期统一管理 +- 🐛 **33.2 发现页面推荐添加菜谱/食材选项卡** — `discover_page.dart` + - **问题**:发现页面推荐部分缺少菜谱和食材切换选项 + - **修复**: + - ✅ 添加 GlassSegmentedControl 组件切换菜谱/食材 + - ✅ 新增食材分类数据加载 `_ingredientCategories` + - ✅ 根据选择类型显示对应分类列表 + - **影响文件**:`discover_page.dart` -- 🐛 **控制器重复注册修复** — `app_binding.dart` - - 移除 MainBinding 中 FavoritesController/ShoppingListController 的重复注册(已在 AppBinding 全局注册) - - 移除 FavoritesBinding 中 FavoritesController/ToolsController 的重复注册 - - 移除 RecipeDetailBinding 中 FavoritesController/ActionController/ShoppingListController 的重复注册 - - 移除 ShoppingBinding 中 ShoppingListController 的重复注册(且与 AppBinding 的 put+permanent 方式冲突) - - 移除 ToolsBinding 中 ToolsController 的重复注册 - - 移除 DiscoverBinding/HotBinding/WhatToEatBinding 中 HotController/WhatToEatController 的重复注册 - - 删除已清空的 Binding 类:MainBinding, DiscoverBinding, HotBinding, WhatToEatBinding, ShoppingBinding, FavoritesBinding, RecipeDetailBinding, ToolsBinding +- 🐛 **33.3 菜品详情页使用完整API获取数据** — `recipe_detail_page.dart` + - **问题**:菜品详情页使用 `act=detail` 接口,数据不完整,picId 显示无 + - **修复**:改用 `fetchFull()` 方法调用 `act=full` 接口获取完整数据 + - **影响文件**:`recipe_detail_page.dart` -- 🔧 **AppBinding 全局控制器统一管理** - - 新增 ToolsController 全局注册(permanent: true)— 多页面使用,应全局管理 - - 新增 HotController 全局注册(permanent: true)— 主标签页+独立页面均使用 - - 新增 WhatToEatController 全局注册(permanent: true)— 主标签页+独立页面均使用 - - 添加分类注释,明确服务层/主题层/核心业务控制器的职责边界 +- 🐛 **33.4 首页下拉刷新手势识别修复** — `home_page.dart` + - **问题**:下拉刷新手势识别不正确 + - **修复**: + - ✅ 移除 CustomScrollView 的 ScrollController + - ✅ 添加 BouncingScrollPhysics 改善手势体验 + - **影响文件**:`home_page.dart` -- 🧹 **页面内联注册清理** - - `favorites_page.dart` — 移除 ToolsController 防御性 Get.put,改为直接 Get.find - - `tools_center_page.dart` — 移除 ToolsController 防御性 Get.put,改为直接 Get.find - - `recipe_detail_page.dart` — 移除 ActionController/FavoritesController 防御性 Get.put,改为直接 Get.find - - `navigation_widgets.dart` — 移除 MainNavigationController 的 Get.isRegistered 检查+Get.put,改为直接 Get.find - - `app_routes.dart` — 移除已删除 Binding 的路由引用 +- 🐛 **33.5 下拉刷新数据不更新修复** — `recipe_repository.dart` + - **问题**:下拉刷新后列表数据不更新,一直显示相同内容 + - **修复**:在 API 调用中添加 `forceRefresh` 参数,强制刷新缓存 + - **影响文件**:`recipe_repository.dart` -### 影响说明 +--- -此修复解决了以下问题: -1. **状态丢失**:Get.put() 对已注册的同类型会替换实例,导致控制器状态数据丢失 -2. **注册方式冲突**:同一控制器在不同 Binding 中混用 put/lazyPut,生命周期不一致 -3. **内存泄漏风险**:重复创建/销毁控制器实例造成不必要的资源消耗 -4. **防御性代码冗余**:页面中 try-catch + Get.put 模式不再需要 +## [0.80.0] - 2026-04-11 -## [0.62.1] - 2026-04-10 +### Fixed — 阶段三十二:全局图片异常 + 底部导航栏黑块修复 -### Fixed — Linter 警告清理 +- 🐛 **32.1 全页面 Exception: Invalid image 彻底修复** — `recipe_image.dart` + - **问题**:搜索页、用餐时段页、分类浏览页等所有使用 RecipeImage 的页面均出现红色异常文本 + - **根因(深度分析)**: + - `_isValidImageData()` 仅检查前8字节头部,无法检测截断/损坏的图片文件 + - 损坏数据通过头部校验 → 存入缓存 → `Image.memory` 解码失败 → 异常泄露 + - `_buildErrorWidget` 引用已确认无效的 `_imageBytes` → 二次异常 → 无 errorBuilder → 泄露到UI + - **彻底修复方案**: + - ✅ 新增 `_canDecodeImage()` 完整解码验证(`instantiateImageCodecFromBuffer`),下载后真实验证图片可解码性 + - ✅ 新增独立状态 `_displayHasError` 隔离显示错误与数据状态,防止状态污染 + - ✅ `_onDisplayError()` 立即清除无效 `_imageBytes = null`,防止复用 + - ✅ `_loadLocalErrorFallback()` 不再将无效数据存入 `_imageBytes` + - ✅ 提取 `_buildSafeImageWidget()` 统一管理 Image.memory + errorBuilder + - ✅ 所有 fallback 路径最终指向 `_buildBlankPlaceholder` 🍽️,彻底切断异常链 + - **影响文件**:`recipe_image.dart` + +- 🐛 **32.2 底部 Tab 栏下层黑块移除** — `navigation_widgets.dart` + - **问题**:主页底部 Tab 栏(首页/收藏/发现/我的)下方出现黑色条带,亮色模式下尤为明显 + - **根因**:`MainTabView` 使用 `Column` 布局,`GlassNavBar`(毛玻璃半透明)作为 Column 子项位于底部。半透明的 nav bar 后方暴露了 Column 剩余区域的不一致背景色 + - **修复方案**: + - ✅ 将布局从 `Column` 改为 `Stack` + `Positioned(bottom:0)` 浮层结构 + - ✅ 页面内容区(IndexedStack)填充上方全部空间 + - ✅ 导航栏浮于最上层,外层 Container 背景色完整覆盖底层 + - ✅ 消除 nav bar 与页面内容之间的背景缝隙/黑块 + - **影响文件**:`navigation_widgets.dart` + +--- + +## [0.79.0] - 2026-04-11 + +### Fixed — 阶段三十一:搜索页相似推荐图片异常修复 + +- 🐛 **31.1 搜索页相似推荐显示"Exception: Invalid image"** — `recipe_image.dart` + - **问题**:搜索无结果时,相似推荐列表的食谱卡片图片区域显示红色错误文本`Exception: Invalid image` + - **根因**:`_buildErrorWidget`方法中存在**异常循环**: + 1. 网络图片下载后通过头部校验(仅检查前8字节)→ 存入`_imageBytes` + 2. `Image.memory(_imageBytes!)`解码完整数据 → 抛出`Invalid image`异常 + 3. `errorBuilder`捕获 → 调用`_buildErrorWidget(isDark)` + 4. ❌ `_buildErrorWidget`发现`_imageBytes != null`,再次用`Image.memory(_imageBytes!)`显示**同一份无效数据**,且**无errorBuilder** + 5. 再次抛异常 → 无捕获 → Flutter红色错误面板泄露到UI + - **修复方案**: + - ✅ `_buildErrorWidget`改用`_localErrorBytes`(本地error.png)替代`_imageBytes` + - ✅ 新增`_buildBlankPlaceholder`方法,提取空白占位逻辑 + - ✅ errorBuilder链路全部指向安全的占位组件,彻底切断异常循环 + - ✅ 即使本地error.png加载失败也会降级为🍽️文字占位 + - **影响文件**:`recipe_image.dart` + +--- + +## [0.78.0] - 2026-04-11 + +### Added — 阶段三十:图片信息卡片功能增强 + +- ✨ **31.1 新增复制图片URL按钮** — `recipe_detail_page.dart` + - **需求**:在复制Picid按钮右边添加复制图片URL按钮 + - **功能实现**: + - 📋 "复制Picid"按钮 - 复制picId到剪贴板 + - 🔗 "复制URL"按钮 - 复制图片链接到剪贴板 + - 💬 点击按钮显示Toast气泡提示 + - **UI优化**: + - 两个按钮并排显示,间距8px + - 使用GestureDetector实现独立点击事件 + - 按钮样式统一:主题色背景+圆角 + - **Toast提示**: + - 复制Picid成功:`Picid 已复制: 7640 ✅` + - 复制URL成功:`图片URL已复制 ✅` + - 无效操作:`此菜谱无有效 Picid` / `此菜谱无图片链接` + - **影响文件**:`recipe_detail_page.dart` + +--- + +## [0.77.0] - 2026-04-11 + +### Fixed — 阶段二十九:详情页图片加载修复 + +- 🐛 **30.1 详情页封面图加载失败** — `recipe_detail_page.dart` + - **问题**:详情页封面图显示error.png,网络图片未正确加载 + - **根因**:`_buildCoverImage`方法使用`RecipeImage.full`时未传递`picId`参数 + - **修复方案**: + - ✅ 添加`picId: _recipe!.picId`参数传递 + - ✅ RecipeImage组件使用picId构建正确的图片URL + - **图片加载链**: + - ① coverUrl(API返回的cover字段) + - ② {picId}a.jpg(高清图) + - ③ {picId}b.jpg(标清图) + - ④ {picId}.jpg(原图) + - ⑤ back.png(网络默认图) + - ⑥ error.png(本地错误图) + - **影响文件**:`recipe_detail_page.dart` + +--- + +## [0.76.0] - 2026-04-11 + +### Optimized — 阶段二十八:API接口优化 + +- ⚡ **29.1 优化详情页接口调用** — `recipe_repository.dart` + - **背景**:`api.php?act=detail` 现已支持 `pic_id` 字段 + - **优化方案**: + - ✅ `fetchDetailWithPicId` 改用 `api.php?act=detail` 接口 + - ✅ 移除对 `api_what_to_eat.php` 的依赖 + - ✅ 统一使用主接口,降低维护成本 + - **API更新**: + - `api.php?act=detail` - **现已支持pic_id字段** + - `api.php?act=list` - 列表接口,轻量级 + - `api_feed.php` - 信息流接口,轻量级 + - **主页信息流**: + - ✅ 已使用轻量级接口(`api_feed.php` + `api.php?act=list`) + - ✅ 避免使用全字段接口(`act=full`) + - **影响文件**:`recipe_repository.dart`, `docs/api/doc/APP_GUIDE.md` + +--- + +## [0.75.0] - 2026-04-11 + +### Fixed — 阶段二十七:Picid数据加载问题修复 + +- 🐛 **28.1 Picid显示为无的根本原因修复** — `recipe_detail_page.dart` + - **问题**:菜谱详情页Picid依旧显示"无",图片无法正确加载 + - **根因**:使用`fetchDetail`方法(act=detail)获取数据,该接口不返回`pic_id`字段 + - **修复方案**: + - ✅ 新增`fetchDetailWithPicId`方法,使用`api_what_to_eat.php?act=detail`接口 + - ✅ 该接口返回包含`pic_id`的完整数据,且比`act=full`更快 + - ✅ 减少服务器负载,提升响应速度 + - 🔍 添加详细调试日志输出picId/cover/code/status等字段 + - **API对比**: + - `act=detail` - 返回基础信息,**不含pic_id** + - `act=full` - 返回完整信息,**包含pic_id但慢** + - `api_what_to_eat.php?act=detail` - 返回详情含pic_id,**推荐使用** + - **测试验证**: + - ✅ 创建`test_pic_id_validation.dart`脚本验证API接口 + - ✅ 验证结果:api_what_to_eat.php返回pic_id=7640 + - ✅ 图片链接测试:7640a.jpg/7640b.jpg可访问,back.png可访问 + - **影响文件**:`recipe_detail_page.dart`, `recipe_repository.dart`, `scripts/test_pic_id_validation.dart` + +--- + +## [0.74.0] - 2026-04-11 + +### Enhanced — 阶段二十六:图片链接完整显示 + +- ✨ **27.1 显示完整图片链接列表** — `recipe_detail_page.dart` + - **需求**:显示所有图片链接,包括高清/标清/原图/默认图 + - **功能实现**: + - 📋 显示完整的图片链接列表(按优先级排序) + - ① Cover - API返回的cover字段(如果有) + - ② 高清 - {picId}a.jpg + - ③ 标清 - {picId}b.jpg + - ④ 原图 - {picId}.jpg + - ⑤ 默认 - back.png + - **UI优化**: + - 每个链接前显示标签(①②③④⑤) + - 链接使用等宽字体(monospace) + - 支持选中复制单个链接 + - 点击卡片复制Picid + - **技术细节**: + - 使用 `imageUrls.asMap().entries.map()` 遍历列表 + - 标签使用Container + 主题色背景 + - 链接使用SelectableText支持选中 + - **影响文件**:`recipe_detail_page.dart` + +--- + +## [0.73.0] - 2026-04-11 + +### Fixed — 阶段二十五:Picid功能Bug修复 + +- 🐛 **26.1 Picid显示为0的问题** — `recipe_detail_page.dart` + - **问题**:所有菜谱的Picid都显示为0 + - **根因**:API返回的pic_id字段可能不存在或为null,_parseInt返回默认值0 + - **修复方案**: + - ✅ 添加有效性检查:`hasValidPicId = picId != null && picId > 0` + - ✅ 无效Picid时显示"无",不显示0 + - ✅ 无有效Picid时图片链接显示"暂无图片链接" + - 🔍 添加调试日志输出picId实际值 + - **影响文件**:`recipe_detail_page.dart` + +- 🐛 **26.2 点击复制卡死闪退** — `recipe_detail_page.dart` + - **问题**:点击Picid卡片复制时应用卡死闪退 + - **根因**:ToastService.show可能因ThemeService未注册而崩溃 + - **修复方案**: + - ✅ 新增 `_showCopyToast()` 方法安全显示Toast + - ✅ 添加mounted检查防止Widget已销毁 + - ✅ Clipboard.setData包裹try-catch防止异常 + - ✅ 失败时显示友好提示而非崩溃 + - **影响文件**:`recipe_detail_page.dart` + +--- + +## [0.72.0] - 2026-04-11 + +### Added — 阶段二十四:菜谱详情页功能增强 + +- ✨ **25.1 Picid显示和复制功能** — `recipe_detail_page.dart` + - **需求**:在菜谱详情页用户名称下方显示图片信息和Picid + - **功能实现**: + - 🖼️ 新增图片信息卡片,显示在作者卡片下方 + - 📋 显示Picid编号(可选中复制) + - 🔗 显示图片链接(可选中复制) + - 📋 点击卡片一键复制Picid到剪贴板 + - 💬 复制成功后显示Toast提示 + - **UI设计**: + - 卡片式设计,带主题色边框 + - 右上角显示"点击复制"提示标签 + - Picid使用等宽字体(monospace)高亮显示 + - 图片链接支持多行显示 + - **技术细节**: + - 导入 `flutter/services.dart` 使用 Clipboard + - 使用 SelectableText 支持文本选中 + - 无Picid时显示"无",但仍显示图片链接 + - **影响文件**:`recipe_detail_page.dart` + +--- +## [0.69.0] - 2026-04-11 + +### Enhanced — 阶段二十一:菜谱详情页完整数据展示 + +- ✨ **22.1 RecipeModel 新增 status 字段 + 时间戳智能解析** — `recipe_model.dart` + - 新增 `status` 字段,解析 API 返回的状态值(0=正常/1=草稿/2=禁用) + - 重构 `_parseTimestamp` 方法,支持多种时间戳格式: + - 13位:毫秒级时间戳(直接使用) + - 10位:秒级时间戳(×1000转毫秒) + - 12位:补齐至毫秒级(×10或×100自动校正) + - 自动年份校验:转换结果不在2000-2100范围时尝试修正 + - 兼容字段名:`create_time`/`created_at`、`update_time`/`updated_at` + +- ✨ **22.2 菜谱详情页显示完整API数据** — `recipe_detail_page.dart` + - **新增状态标识**:带颜色标签显示菜谱状态(✅正常/⏸️草稿/🚫禁用) + - **时间信息完善**:创建时间 + 更新时间(自动格式化为可读日期) + - **数据完整性**:确保API返回的所有字段都在UI中展示 + - 状态行使用iOS风格标签设计,不同状态对应不同颜色 + +- 🐛 **22.3 时间戳转换Bug修复** + - 问题:12位时间戳(如146085838541)被错误识别为秒级 + - 原因:判断条件 `>1e12` 对12位数值失效 + - 修复:按位数长度精确判断,12位时补零并验证年份范围 + - 测试验证:146085838541 → 2016-04-17 09:59 ✅ + +- ✅ **22.4 测试脚本** — `scripts/test_recipe_detail_parsing.dart` + - 覆盖所有新增字段的解析测试 + - 验证时间戳转换准确性 + - 确认分类食材/作者/状态等数据完整性 -- 🧹 **代码规范警告修复** - - `glass_animations.dart` - null 检查语法修复 - - `nutrition_center_page.dart` - 字符串插值优化 - - `favorites_page.dart` - separatorBuilder 参数规范化 - - `tools_center_page.dart` - separatorBuilder 参数规范化 - - `ingredient_detail_page.dart` - separatorBuilder 参数规范化 - - `skeleton_loader.dart` - separatorBuilder 参数规范化 - - `meal_time_recommend_page.dart` - separatorBuilder + 字符串插值修复 - - `meal_planner_page.dart` - separatorBuilder 参数规范化 - - `allergen_checker_page.dart` - separatorBuilder 参数规范化 -### Added — 代码分析与风险评估 -- 📋 **CODE_ANALYSIS.md** 文档新增 - - 闪退卡死风险点分析(高/中/低风险分级) - - 性能优化机会清单 - - 新功能建议与优先级 ## [0.62.0] - 2026-04-10 diff --git a/analysis_options.yaml b/analysis_options.yaml index b0653c0..7ddfbc4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -12,6 +12,7 @@ include: package:flutter_lints/flutter.yaml analyzer: exclude: - packages/** + - scripts/** linter: # The lint rules applied to this project can be customized in the diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9bd0158..c8db683 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -36,6 +36,13 @@ android { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") + + // 启用代码混淆 + isMinifyEnabled = true + // 启用资源压缩 + isShrinkResources = true + // 指定 ProGuard 规则文件 + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..56d19a7 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,29 @@ +# Flutter ProGuard Rules +# 2026-04-11 | ProGuard Rules | 解决 release 包白屏问题 + +# Flutter 引擎相关 +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } + +# Flutter 嵌入相关 +-keep class io.flutter.embedding.** { *; } + +# 保持 Flutter 生成的类 +-keep class androidx.lifecycle.** { *; } + +# 保持反射调用的类 +-keepattributes *Annotation* +-keepattributes Signature +-keepattributes InnerClasses + +# 保持泛型信息 +-keepclassmembers,allowobfuscation class * { + @com.google.gson.annotations.SerializedName ; +} + +# 避免 R8 完全模式问题 +-dontwarn io.flutter.embedding.** diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6e90ef6..a7445a1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ + android:icon="@mipmap/ic_launcher" + android:networkSecurityConfig="@xml/network_security_config"> + + + + eat.wktyl.com + + diff --git a/assets/data/photos/error.png b/assets/data/photos/error.png deleted file mode 100644 index c9fe6c0..0000000 Binary files a/assets/data/photos/error.png and /dev/null differ diff --git a/assets/data/categories.json b/assets/json/categories.json similarity index 100% rename from assets/data/categories.json rename to assets/json/categories.json diff --git a/assets/data/filter_steps.json b/assets/json/filter_steps.json similarity index 100% rename from assets/data/filter_steps.json rename to assets/json/filter_steps.json diff --git a/assets/data/home_list.json b/assets/json/home_list.json similarity index 100% rename from assets/data/home_list.json rename to assets/json/home_list.json diff --git a/assets/data/home_recommend.json b/assets/json/home_recommend.json similarity index 100% rename from assets/data/home_recommend.json rename to assets/json/home_recommend.json diff --git a/assets/data/hot_month.json b/assets/json/hot_month.json similarity index 100% rename from assets/data/hot_month.json rename to assets/json/hot_month.json diff --git a/assets/data/hot_today.json b/assets/json/hot_today.json similarity index 100% rename from assets/data/hot_today.json rename to assets/json/hot_today.json diff --git a/assets/data/hot_total.json b/assets/json/hot_total.json similarity index 100% rename from assets/data/hot_total.json rename to assets/json/hot_total.json diff --git a/assets/data/random_recipe.json b/assets/json/random_recipe.json similarity index 100% rename from assets/data/random_recipe.json rename to assets/json/random_recipe.json diff --git a/assets/data/smart_recipe.json b/assets/json/smart_recipe.json similarity index 100% rename from assets/data/smart_recipe.json rename to assets/json/smart_recipe.json diff --git a/assets/data/tags.json b/assets/json/tags.json similarity index 100% rename from assets/data/tags.json rename to assets/json/tags.json diff --git a/assets/photos/error.png b/assets/photos/error.png new file mode 100644 index 0000000..78983d7 Binary files /dev/null and b/assets/photos/error.png differ diff --git a/docs/api/doc/APP_GUIDE.md b/docs/api/doc/APP_GUIDE.md index 6986d3a..9d3c782 100644 --- a/docs/api/doc/APP_GUIDE.md +++ b/docs/api/doc/APP_GUIDE.md @@ -1018,7 +1018,7 @@ GET api_what_to_eat.php?act=detail&code=CP032892 |------|-----------|---------| | `id` | 详情查询、收藏、分享链接 | `api.php?act=detail` | | `code` | 二维码、短链接、语音搜索 | `api_what_to_eat.php?act=detail&code=` | -| `pic_id` | 图片资源关联、新旧系统迁移 | `api.php?act=full`、`api_what_to_eat.php?act=detail`、`api_feed.php` | +| `pic_id` | 图片资源关联、新旧系统迁移 | `api.php?act=detail`、`api.php?act=full`、`api_what_to_eat.php?act=detail`、`api_feed.php` | | `title` | 搜索、分享标题、列表展示 | `api.php?act=search` | | `intro` | 用餐时段筛选、列表预览 | 客户端过滤 | | `category` | 分类筛选、面包屑导航 | `api.php?act=list&cate_id=` | diff --git a/docs/api/doc/act=full&id=32891 b/docs/api/doc/act=full&id=32891 index b2b672f..246c021 100644 --- a/docs/api/doc/act=full&id=32891 +++ b/docs/api/doc/act=full&id=32891 @@ -1,518 +1,7 @@ -{ - "code": 200, - "message": "success", - "data": { - "id": 32891, - "code": "CP032891", - "pic_id": 7640, - "title": "海带焖木耳", - "intro": "早餐、中餐、晚餐", - "content": "1.海带洗净,切去梗,切成3厘米见方的块;\n2.海带用沸水焯过捞起;\n3.黑木耳用水发好,剔去杂质,洗净;\n4.葱白切段,姜拍松;\n5.油豆腐切成4厘米见方的块;\n6.炒锅放旺火上,倒入花生油,烧热,煸生姜、葱段,倒入海带、木耳、豆腐,加料酒、酱油、白糖、香醋及适量水,烧30分钟;\n7.调入味精颠翻装盘,淋香油,撒胡椒粉,即成。", - "cover": "", - "status": 0, - "create_time": 146085838541, - "update_time": 146085838541, - "category": { - "id": 42, - "name": "家常菜", - "alias": "", - "hierarchy": [ - { - "id": 11, - "name": "菜谱", - "alias": "caipu", - "level": 3 - }, - { - "id": 12, - "name": "中国菜", - "alias": "", - "level": 2 - }, - { - "id": 42, - "name": "家常菜", - "alias": "", - "level": 1 - } - ] - }, - "author": { - "id": 1, - "name": "520kiss", - "alias": "", - "email": "null@null.com", - "homepage": "" - }, - "tags": [], - "ingredients": { - "main": [ - { - "ingredient_id": 253537, - "name": "海带(鲜}", - "amount": "250克", - "sort": 0, - "detail_id": 0, - "detail": null - } - ], - "auxiliary": [ - { - "ingredient_id": 253553, - "name": "木耳(干}", - "amount": "30克", - "sort": 1, - "detail_id": 0, - "detail": null - }, - { - "ingredient_id": 253568, - "name": "油豆腐", - "amount": "100克", - "sort": 2, - "detail_id": 364, - "detail": { - "alias": [], - "usage_tip": [ - "一般人皆可食用油豆腐相对于其他豆制品不易消化,经常消化不良、胃肠功能较弱的人慎食。" - ], - "introduction": "油豆腐是豆腐的炸制食品,色泽金黄,易吸收汤汁,常被用做入汤的原料。", - "nutrition": "油豆腐富含优质蛋白、多种氨基酸、不饱和脂肪酸及磷脂等,铁、钙的含量也很高。", - "guidance": "炸制油豆腐,火要大,这样才会里嫩外酥。", - "effect": "", - "other": "", - "allergen": [ - "豆腐", - "豆" - ], - "allergen_type": [ - "豆类" - ] - } - } - ], - "seasoning": [ - { - "ingredient_id": 253585, - "name": "料酒", - "amount": "25克", - "sort": 3, - "detail_id": 1208, - "detail": { - "alias": [], - "usage_tip": [ - "一般人群均可食用" - ], - "introduction": "料酒就是专门用于烹饪调味的酒。在我国的应用已有上千年的历史,日本、美国、欧洲的某些国家也有使用料酒的习惯。从理论上来说,啤酒、白酒、黄酒、葡萄酒、威士忌都可用作料酒。但人们经过长期的实践、品尝后发现,不同的料酒所烹饪出来的菜肴风味相距甚远。经过反复试验,人们发现以黄酒烹饪为最佳。酒为人们所喜欢的饮料,品种极多,作调味品的主要是黄酒。福建、山东、浙江等地都有生产,以浙江沼兴所产质量较好。黄酒是用糯米或小米酿造而成的,其成分主要有酒精、糖分、糊精、有机酸类、氨基酸、酯类、醛类、杂醇油及浸出物等。其酒精浓度低,含量在15%以下,而酯类含量高,富含氨基酸,所以香味浓郁,味道醇厚,在烹制菜肴中使用广泛。黄酒的调味作用主要为去腥、增香。", - "nutrition": "1. 动物性原料作菜肴时,因为肉、脏腑、鱼类等的组织中和鱼类身体表面的粘液里含有腥臊异味,这些物质在加热时能被酒中的酒精所溶解,并随气化的酒精一齐挥发,这样就除去了腥味;\n2. 黄酒中的氨基酸还能与糖结合成芳香醛,产生诱人的香气;\n3. 黄酒中所含的酯类也有香气,所以烹调中加入黄酒,能使菜肴除去异味,且香味大增;\n4. 黄酒中还含有多种维生素和微量元素,而且使菜肴的营养更加丰富;\n5. 在烹饪肉、禽、蛋等菜肴时,调入黄酒能渗透到食物组织内部,溶解微量的有机物质,从而使菜肴质地松嫩;\n6. 温饮黄酒,可帮助血液循环,促进新陈代谢,具有补血养颜,活血祛寒,通经活络,能有效抵御寒冷刺激,预防感冒;\n7. 黄酒还可作为药引子食用。", - "guidance": "烹调菜肴时不要放得过多,以免料酒味太重而影响菜肴本身的滋味。", - "effect": "", - "other": "", - "allergen": [ - "酒", - "料酒" - ], - "allergen_type": [ - "调味品类" - ] - } - }, - { - "ingredient_id": 253604, - "name": "酱油", - "amount": "20克", - "sort": 4, - "detail_id": 1209, - "detail": { - "alias": [ - "豉油", - "酱汁", - "豉汁" - ], - "usage_tip": [ - "一般人群均可食用" - ], - "introduction": "酱油俗称豉油,主要由大豆,淀粉、小麦、食盐经过制油、发酵等程序酿制而成的。酱油的成分比较复杂,除食盐的成分外,还有多种氨基酸、糖类、有机酸、色素及香料民分。以咸味为主,亦有鲜味、香味等。它能增加和改善菜肴的口味,还能增添或改变菜肴的色泽。我国人民在数千年前就已经掌握酿制工艺了。酱油一般有老抽和生抽两种:老抽较咸,用于提色;生抽用于提鲜。", - "nutrition": "1. 烹调食品时加入一定量的酱油,可增加食物的香味,并可使其色泽更加好看,从而增进食欲;\n2. 酱油的主要原料是大豆,大豆及其制品因富含硒等矿物质而有防癌的效果;\n3. 酱油含有多种维生素和矿物质,可降低人体胆固醇,降低心血管疾病的发病率,并能减少自由基对人体的损害;\n4. 酱油可用于水、火烫伤和蜂、蚊等虫的蜇伤,并能止痒消肿。", - "guidance": "1. 要食用“酿造”酱油,而不要吃“配制”酱油;\n2. “餐桌酱油”拌凉菜用,“烹调酱油”未经加热不宜直接食用;\n2. 酱油应在菜肴将要出锅时加入,不宜长时间加热。", - "effect": "", - "other": "", - "allergen": [], - "allergen_type": [] - } - }, - { - "ingredient_id": 253620, - "name": "味精", - "amount": "4克", - "sort": 5, - "detail_id": 1207, - "detail": { - "alias": [ - "味素", - "味之素" - ], - "usage_tip": [ - "一般成年人均可食用记忆障碍患者、高血压不宜食用;孕妇及婴幼儿不宜吃味精;老人和儿童也不宜多食。" - ], - "introduction": "味精是烹调中常用的鲜味调味品,有固体味精和液体味精两种。液体味精是未经炼成颗粒的味精原液,饮食业中以用固体味精为常见。味精的化学名称叫谷氨酸钠,由大豆、小麦面粉及其他含蛋白较高的物质,经由淀粉发酵法制成,除含有谷氨酸钠外还含有少量的食盐,以含谷氨酸钠的多少(90%、95%、90%、80%),分成各种规格。全国各地均有生产。", - "nutrition": "1. 味精对人体没有直接的营养价值,但它能增加食品的鲜味,引起人们食欲,有助于提高人体对食物的消化率;\n2. 味精中的主要成分谷氨酸钠还具有治疗慢性肝炎、肝昏迷、神经衰弱、癫痫病、胃酸缺乏等病的作用。", - "guidance": "1. 对用高汤烹制的菜肴,不必使用味精,因为高汤本身已具有鲜、香、清的特点,味精则只有一种鲜味,而它的鲜味和高汤的鲜味也不能等同,如使用味精,会将本味掩盖,致使菜肴口味不伦不类;\n2. 对酸性菜肴,如:糖醋、醋熘、醋椒菜类等,不宜使用味精,因为味精在酸性物质中不易溶解,酸性越大溶解度越低,鲜味的效果越差;\n3. 拌凉菜使用晶体味精时,应先用少量热水化开,然后再浇到凉菜上,效果较好,因味精在45℃时才能发挥作用,如果用晶体直接拌凉菜,不易拌均匀,影响味精的提鲜作用;\n4. 作菜使用味精,应在起锅时加入,因为在高温下,味精会分解为焦谷氨酸钠,即脱水谷氨酸钠,不但没有鲜味,而且还会产生轻微的毒素,危害人体;\n5. 味精使用时应掌握好用量,并不是多多益善,它的水稀释度是3000倍,人对味精的味觉感为\n0. 033%,在使用时,以1500倍左右为适宜,如投放量过多,会使菜中产生似成非成,似涩非涩的怪味,造成相反的效果;\n6. 味精在常温下不易溶解,在 70~90度时溶解最好,鲜味最足,超过100度时味精就被水蒸气挥发,超过130度时,即变质为焦谷氨酸钠,不但没有鲜味,还会产生毒性,对炖、烧、煮、熬、蒸的菜,不宜过早放味精,要在将出锅时放入;\n7. 在含有碱性的原料中不宜使用味精,回味精遇碱会化合成谷氨酸二钠,会产生氨水臭味。", - "effect": "", - "other": "", - "allergen": [ - "味精" - ], - "allergen_type": [ - "调味品类" - ] - } - }, - { - "ingredient_id": 253635, - "name": "姜", - "amount": "4克", - "sort": 6, - "detail_id": 1, - "detail": { - "alias": [ - "生姜", - "黄姜", - "均姜" - ], - "usage_tip": [ - "1. 适宜伤风感冒、寒性痛经、晕车晕船者食用。", - "2. 阴虚内热及邪热亢盛者忌食。" - ], - "introduction": "姜属姜科,为植物姜的干燥根茎或鲜根茎,多年生草本植物。原产印度、马来西亚,我国自古栽培,周朝食用。姜供食用的部位为不规则的块茎,呈灰白或黄色,具有辛辣味。姜按用途和收获季节不同而有嫩姜和老姜之分。嫩姜多在八月份挖掘,一般含水多,纤维少,辛辣味淡薄,除做调味品外,尚可炒食,做姜糖等;老姜多在十一月份挖掘,水分少,辛辣味浓,主要用做调味。姜是一种极为重要的调味品,同时也可作为蔬菜单独食用,而且还是一味重要的中药材。它可将自身的辛辣味和特殊芳香渗入到菜肴中,使之鲜美可口,味道清香。", - "nutrition": "生姜还具有解毒杀菌的作用,日常我们在吃松花蛋或鱼蟹等水产时,通常会放上一些姜末、姜汁。人体在进行正常新陈代谢生理功能时,会产生一种有害物质氧自由基,促使机体发生癌症和衰老。生姜中的姜辣素进入体内后,能产生一种抗氧化本酶,它有很强的对付氧自由基的本领,比维生素E还要强得多。所以,吃姜能抗衰老,老年人常吃生姜可除“老年斑”。生姜的提取物能刺激胃粘膜,引起血管运动中枢及交感神经的反射性兴奋,促进血液循环,振奋胃功能,达到健胃、止痛、发汗、解热的作用。姜的挥发油能增强胃液的分泌和肠壁的蠕动,从而帮助消化;生姜中分离出来的姜烯、姜酮的混合物有明显的止呕吐作用。生姜提取液具有显著抑制皮肤真菌和杀来头阴道滴虫的功效,可治疗各种痈肿疮毒。生姜有抑制癌细胞活性、降低癌的毒害作用。", - "guidance": "1. 吃饭不香或饭量减少时吃上几片姜或者在菜果放上一点嫩姜,都能改善食欲,增加饭量,所以俗话说:“饭不香,吃生姜”。\n2. 姜可煎汤内服,佐料,入菜炒食,或切片炙穴位。老姜可做调料或配料;嫩姜可用于炒、拌、爆等,如“嫩姜炒牛肉丝”、“嫩姜爆鸭丝”等。\n3. 吃姜一次不宜过多,以免吸收大量姜辣素,在经肾脏排泄过程中会刺激肾脏,并产生口干、咽痛、便秘等“上火”症状。\n4. 烂姜、冻姜不要吃,因为姜变质后会产生致癌物,由于姜性质温热,有解表功效,所以只能在受寒的情况下作为食疗应用。烹调用途:生姜重要的调料品,因为其味清辣,只将食物的异味挥散,而不将食品混成辣味,宜作荤腥菜的矫味品,亦用于糕饼糖果制作,如姜饼、姜糖等。", - "effect": "生姜味辛、性微温,入脾、胃、肺经;具有发汗解表,温中止呕,温肺止咳,解毒的功效;主治外感风寒、胃寒呕吐、风寒咳嗽、腹痛腹泻、中鱼蟹毒等病症。", - "other": "", - "allergen": [ - "姜" - ], - "allergen_type": [ - "蔬菜类" - ] - } - }, - { - "ingredient_id": 253650, - "name": "大葱", - "amount": "10克", - "sort": 7, - "detail_id": 2, - "detail": { - "alias": [ - "葱", - "青葱", - "四季葱", - "事菜" - ], - "usage_tip": [ - "1. 脑力劳动者更宜;", - "2. 患有胃肠道疾病特别是溃疡病的人不宜多食;另外葱对汗腺刺激作用较强,有腋臭的人在夏季应慎食;表虚、多汗者也应忌食;过多食用葱还会损伤视力。" - ], - "introduction": "葱属百合科,是多年生草本植物葱的茎与叶,上部为青色葱叶,下部为白色葱白。原产于西伯利亚,我国栽培历史悠久,分布广泛,而以山东、河北、河南等省为重要产地。大葱耐寒抗热,适应性强,四季均可上市。普通大葱,原产我国,遍及南北各地。叶圆而中空,叶鞘基部抱合成“假茎”,幼嫩时叶和葱白都能食用。根据葱白的长短又分为两个类型。大葱植株高大,葱白洁白而味甜,在北方栽培较多。葱是日常厨房里的必备之物,北方以大葱为主,它不仅可作调味之品,而且能防治疫病,可谓佳蔬良药。大葱多用于煎炒烹炸;南方多产小葱,是一种常用调料,又叫香葱,一般都是生食或拌凉菜用。", - "nutrition": "1. 生葱像洋葱、大葱一样,含烯丙基硫醚。而烯丙基硫醚会刺激胃液的分泌,且有助于食欲的增进。同时与维生素B1含量较多的食物一起摄取时,维生素B1所含的淀粉及糖质会变为热量,而提高恢复疲劳的作用。\n2. 葱叶部分要比葱白部分含有更多的维生素A、维C及钙。葱中含有相当量的维生素C,有舒张小血管,促进血液循环的作用,有助于防止血压升高所致的头晕,使大脑保持灵活和预防老年痴呆的作用。\n3. 经常吃葱的人,即便脂多体胖,但胆固醇并不增高,而且体质强壮。葱含有微量元素硒,并可降低胃液内的亚硝酸盐含量,对预防胃癌及多种癌症有一定作用。\n4. 葱含有具有刺激性气味的挥发油和辣素,能祛除腥腥膻等油腻厚味菜肴中的异味,产生特殊香气,并有较强的杀菌作用,可以刺激消化液的分泌,增进食欲。挥发性辣素还通过汗腺、呼吸道、泌尿系统排出时能轻微刺激刺激相关腺体的分泌,而起到发汗、祛痰、利尿作用。是治疗感冒的中药之一。\n5. 葱还有降血脂、降血压、降血糖的作用,如果与蘑菇同食可以起到促进血液循环的作用。", - "guidance": "1. 每天食用葱,对身体有益。葱可生吃,也可凉拌当小菜食用,作为调料,多用于荤、腥、膻、以及其他有异味的菜肴、汤羹中,对没有异味的菜肴、汤羹也起增味增香作用。\n2. 根据主料的不同,可切成葱段和葱末掺合使用,均不宜煎、炸过久。\n3. 葱叶因富含维生素A原,不应轻易丢弃不用。\n4. 葱中含有的烯丙基硫醚由于是属于挥发性,因此泡在水里或煮得过久,都会使其效果丧失。\n5. 在加入味增汁熄火之后,再洒上葱花,即可使香味更可口,且可发挥烯丙基硫醚的效果。\n6. 葱与维生素B1含量较多的食品一起摄取。因为具有消除臭味的作用,因此像猪肉或羊肉等带有腥味的菜肴务必要使用葱来调味。", - "effect": "葱味辛、性温;能通阳活血、驱虫解毒、发汗解表;主治风寒感冒轻症、痈肿疮毒、痢疾脉微、寒凝腹痛、小便不利等病症。对感冒、风寒、头痛、阴寒腹痛、虫积内阻、痢疾等有较好的治疗作用。", - "other": "", - "allergen": [ - "葱" - ], - "allergen_type": [ - "蔬菜类" - ] - } - }, - { - "ingredient_id": 253665, - "name": "醋", - "amount": "10克", - "sort": 8, - "detail_id": 1211, - "detail": { - "alias": [ - "苦酒", - "淳酢", - "醯", - "酢" - ], - "usage_tip": [ - "一般人群均可食用脾胃湿盛、外感初起者忌服;胃溃疡和胃酸过多者不宜食醋。" - ], - "introduction": "醋是一种发酵的酸味液态调味品,以含淀粉类的粮食(高粱、黄米、糯米、籼米等)为主料,谷糠、稻皮等为辅料,经过发酵酿造而成。醋在烹调中为主要的调味品之一,以酸味为主,且有芳香味,用途较广,是糖醋味的主要原料。它能去腥解腻,增加鲜味和香味,能在食物加热过程中使维生素C减少损失,还可使烹饪原料中钙质溶解而利于人体吸收。比较著名的品种有江苏镇江的香醋和山西的老陈醋等,常用于溜菜、拌菜及腥味较重的菜肴中。食醋因原料和制作方法的不同,可分为发酵醋和人工合成醋两种,其品种主要有米醋、熏醋、白醋等。米醋主要原料为高粱、黄米、麸皮、米糠、盐,经醋曲发酵后制成,呈浅棕色,香味浓郁,质量较好,适合于蘸食和炒菜;熏醋原料除无黄米外,基本与米醋原料相同,发酵后略加花椒、桂皮等熏制而成,颜色较深,以存放时间长者为好,适合于蘸食和炒菜;白醋(又称醋精)为冰醋酸加水稀释而成,醋酸的含量高于米醋等,酸味大,无香味。浓醋酸有一定的腐蚀作用,使用时应根据需要稀释和控制用量。烹调菜肴时加点醋,不仅使菜肴脆嫩可口,祛除腥膻味,还能保护其中的营养素。但是正在服用某些药物如:磺胺类药、碱性药、抗生素、解表发汗的中药的人不宜食醋。", - "nutrition": "1. 醋可以开胃,促进唾液和胃液的分泌,帮助消化吸收,使食欲旺盛,消食化积;\n2. 醋有很好的抑菌和杀菌作用,能有效预防肠道疾病、流行性感冒和呼吸疾病;\n3. 醋可软化血管、降低胆固醇,是高血压等心脑血管病人的一剂良方;\n4. 醋对皮肤、头发能起到很好的保护作用,中国古代医学就有用醋入药的记载,认为它有生发、美容、降压、减肥的功效;\n5. 醋可以消除疲劳,促进睡眠,并能减轻晕车、晕船的不适症状;\n6. 醋还能减少胃肠道和血液中的酒精浓度,起到醒酒的作用;\n7. 醋还有使鸡骨、鱼翅软化,促进钙吸收的作用。", - "guidance": "1. 吃饺子蘸醋或食用醋较多的菜肴后应及时漱口以保护牙齿;\n2. 作菜时,加醋的最佳时间是在两头,即原料入锅后马上加醋及菜肴临出锅前加醋,第一次应多些,第二次应少些;\n3. 醋可以用于需要去腥解腻的原料,如烹制水产品或肚、肠、心等,可消除腥臭和异味,对一些腥臭较重的原料还可以提前用醋浸渍;\n4. 醋用于烹制带骨的原料,如排骨、鱼类等,可使骨刺软化,促进骨中的矿物质如钙、磷溶出,增加营养成分。", - "effect": "醋味酸苦、性温,入肝、胃经;有散瘀,止血,解毒,杀虫的功效;主治产后血晕、黄疸、黄汗、吐血、衄血、大便下血、痈疽疮肿,又可解鱼肉菜毒。", - "other": "中国古代酸味调味应用较多,醋传为造酒时所创制,《四民月令》已载有作醋方法,至北魏《齐民要术》,其中制醋法已达20余种。以后各代均有名醋出现。至今醋仍为开门七件事之一,在生活中占有重要地位。", - "allergen": [], - "allergen_type": [] - } - }, - { - "ingredient_id": 253678, - "name": "白砂糖", - "amount": "10克", - "sort": 9, - "detail_id": 769, - "detail": { - "alias": [ - "砂糖", - "石蜜", - "白霜糖", - "白糖" - ], - "usage_tip": [ - "一般人群均可食用" - ], - "introduction": "糖是用甘蔗或甜菜等植物加工而成的一种调味品,其主要成分是蔗糖。白砂糖是食糖中质量最好的一种。其颗粒为结晶状,均匀,颜色洁白,甜味纯正,甜度稍低于红糖。烹调中常用。绵白糖为粉末状,适合于烹调之用,甜度与白砂糖差不多。绵白糖有精制绵白糖和土法制的绵白糖两种。前者色泽洁白,晶粒细软,质量较好;后者色泽微黄稍暗,质量较差。白砂糖和绵白糖只是结晶体大小不同,白砂糖的结晶颗粒大,含水分很少,而绵白糖的结晶颗粒小,含水分较多。广东、福建、台湾等省和东北地区是我国主要产糖区。糖是重要的调味品,能增加菜肴的甜味及鲜味,增添制品的色泽,为制作菜肴特别是甜菜品种的主要调味原料。", - "nutrition": "1. 适当食用白糖有助于提高机体对钙的吸收,但过多就会妨碍钙的吸收;\n2. 吃糖后应及时漱口或刷牙,以防龋齿的产生;3 .糖尿病病人不易直接食用食糖,最好是以甜味剂替代。", - "guidance": "1. 炒菜时不小心把盐放多了,加入适量白糖,就可解咸;\n2. 糖很容易生螨,存放日久的糖不要生吃,应煮开后食用。", - "effect": "白砂糖味甘、性平,归脾、肺经;有润肺生津、止咳、和中益肺、舒缓肝气、滋阴、调味、除口臭、解盐卤毒之功效。", - "other": "制糖为我国首创,早在三千多年前我国就有用谷物制作饴糖的记载。根据《齐民要术》的记载可知后汉时我国已经生产蔗糖和冰糖了。唐贞观年间我国自印度传入熬糖法后,改进了工艺,蔗糖质量有所提高。", - "allergen": [], - "allergen_type": [] - } - }, - { - "ingredient_id": 253690, - "name": "香油", - "amount": "5克", - "sort": 10, - "detail_id": 846, - "detail": { - "alias": [ - "麻油", - "芝麻油" - ], - "usage_tip": [ - "老少皆宜" - ], - "introduction": "芝麻油Sesame oil,简称麻油,俗称香油,是小磨香油和机制香油的统称,亦即具有浓郁或显著香味的芝麻油。在加工过程中,芝麻中的特有成分经高温炒料处理后,生成具有特殊香味的物质,致使芝麻油具有独特的香味,有别于其它各种食用油,故称香油。按加工工艺不同,香尚未分为小磨香油和机制香油两种。芝麻(Sesamum indicum)可能原产于非洲一带,产出的油用于烹饪并加在沙拉里,在中菜里也很受欢迎。", - "nutrition": "1. 延缓衰老:香油中含丰富的维生素E,具有促进细胞分裂和延缓衰老的功能;\n2. 保护血管:香油中含有40%左右的亚油酸、棕榈酸等不饱和脂肪酸,容易被人体分解吸收和利用,以促进胆固醇的代谢,并有助于消除动脉血管壁上的沉积物;芝麻油是一种促凝血药,用于治疗血小板减少性紫癜和出血性素质有一定效果;\n3. 润肠通便;\n4. 减轻烟酒毒害:有抽烟习惯和嗜酒的人经常喝点香油,可以减轻烟对牙齿、牙龈、口腔黏膜的直接刺激和损伤,以及肺部烟斑的形成,同时对尼古丁的吸收也有相对的抑制作用。饮酒之前喝点香油,则对口腔、食道、胃贲门和胃黏膜起到一定的保护作用;\n5. 保护嗓子:常喝香油能增强声带弹性,使声门张合灵活有力,对声音嘶哑、慢性咽喉炎有良好的恢复作用;\n6. 从芝麻中榨出香油中所含的卵磷脂都是益寿延年抗衰老的上佳成分,是中老年人最好的冬令补品;", - "guidance": "", - "effect": "芝麻油有利于食物的消化吸收,有延缓衰老、保护血管、润肠通便、减轻烟酒毒害、保护嗓子的功效;对口腔溃疡、牙周炎、牙龈出血、咽喉发炎均有很好的改善作用。", - "other": "", - "allergen": [], - "allergen_type": [] - } - }, - { - "ingredient_id": 253702, - "name": "胡椒粉", - "amount": "2克", - "sort": 11, - "detail_id": 1210, - "detail": { - "alias": [], - "usage_tip": [ - "一般人群均可食用消化道溃疡、咳嗽咯血、痔疮、咽喉炎症、眼疾患者慎食。" - ], - "introduction": "胡椒为热带植物胡椒树的果实,主要产在印度、越南、印尼、泰国、新加坡等国,我国广东省海南岛也有生产。胡椒味辛辣芳香,性热,除可去腥增香外,还有除寒气、消积食的效用,但多食则刺激胃粘膜而引起充血。胡椒粉是用干胡椒碾压而成,有白胡椒粉和黑胡椒粉两种。黑胡椒粉是未成熟果实加工而成,白胡椒粉是果实完全成熟后采摘加工而成。", - "nutrition": "1. 胡椒的主要成分是胡椒碱,也含有一定量的芳香油、粗蛋白、粗脂肪及可溶性氮,能祛腥、解油腻,助消化;\n2. 胡椒的气味能增进食欲;\n3. 胡椒性温热,对胃寒所致的胃腹冷痛、肠鸣腹泻有很好的缓解作用,并治疗风寒感冒;\n4. 胡椒有防腐抑菌的作用,可解鱼虾肉毒;\n5. 黑胡椒的辣味比白胡椒强烈,香中带辣,祛腥提味,更多的用于烹制内脏、海鲜类菜肴;\n6. 白胡椒的药用价值较大,可散寒、健胃等,可以增进食欲、助消化,促发汗;还可以改善女性白带异常及癫痫症。", - "guidance": "", - "effect": "胡椒味辛、性热,入胃、大肠经;有温中下气,消痰解毒的功效;主治寒痰食积、脘腹冷痛、反胃、呕吐清水、泄泻、冷痢;外敷治疮肿、毒蛇咬伤、犬咬伤;又可解食物毒。1.温中散寒:用于胃寒所致的胃脘痛、呕吐、以及腹冷所致的泄泻、肠鸣;2.醒脾开胃:本品小剂量能增进食欲,对胃口差、消化不良有治疗作用。", - "other": "胡椒始见载于唐代《酉阳杂俎》《唐本草》诸书,传为唐僧西域取经携回。以后历代本草均有记述,多供药用,亦用于食品调味。", - "allergen": [ - "胡椒" - ], - "allergen_type": [ - "调味品类" - ] - } - }, - { - "ingredient_id": 253710, - "name": "花生油", - "amount": "30克", - "sort": 12, - "detail_id": 849, - "detail": { - "alias": [ - "落花生油", - "果油" - ], - "usage_tip": [ - "适合所有人,特别是中老年人食用。" - ], - "introduction": "花生油Peanut oil,为豆科植物花生的种子榨出之脂肪油,淡黄透明,色泽清亮,气味芬芳,滋味可口,是一种比较容易消化的食用油。可提供给人体大量营养,含多种脂肪酸的甘油酯,可增加食品的美味,是构成人体内多种组织成分的重要原料。是目前我国主要的食用植物油之一,可用于炒、煎、炸各种菜肴和食品。花生的油脂含量约有50%,很适合拌沙拉或作为炸油。也用来制植物奶油或鱼罐头。", - "nutrition": "1. 中国预防医学科学院经研究证实,花生油含锌量是色拉油、粟米油、菜籽油、豆油的许多倍。虽然补锌的途径很多,但油脂是人们日常必需的补充物,所以食用花生油特别适宜于大众补锌;\n2. 花生油中还含有多种抗衰老成分,有延缓脑功能衰老的作用。花生油还具有健脾润肺,解积食、驱脏虫的功效;\n3. 营养专家还在花生油中发理了3种有益寿延年于心脑血管的保健成分;白藜芦醇、单不饱和脂肪酸和β-谷固醇,实验证明,这几种物质是肿瘤类疾病的化学预防剂,也是降低血小板聚集、防治动脉硬化及心脑血管疾病的化学预防剂;\n4. 是中老年人理想的食用油脂之一,花生油中的胆碱,还可改善人脑的记忆力,延缓脑功能衰退。", - "guidance": "1. 花生油热量高,脂肪量大,不宜过量食用,否则对心脑血管还是会有一定影响,而且容易发胖。\n2. 花生油油耐高温,除炒菜外适合于煎炸食物。\n3. 用花生油炒菜,在油加热后,先放盐,在油中爆约30秒,可除去花生油中可能存在的黄曲霉素。\n4. 植物油可防止粥沫:煮稀饭时,往锅里滴几滴花生油,并改用文火,稀饭就不会有沫子外溢了。", - "effect": "花生油味甘、性平,入脾、肺、大肠经;可补脾润肺、润肠下虫;花生油熟食,有润肠逐虫之功效,可治疗蛔虫性肠梗阻。", - "other": "", - "allergen": [ - "花生" - ], - "allergen_type": [ - "坚果类" - ] - } - } - ] - }, - "allergens": [ - "豆腐", - "豆", - "酒", - "料酒", - "味精", - "姜", - "葱", - "胡椒", - "花生" - ], - "nutrition": [ - { - "name": "叶酸", - "value": 13.98, - "unit": "微克" - }, - { - "name": "核黄素", - "value": 0.28, - "unit": "毫克" - }, - { - "name": "烟酸", - "value": 5.66, - "unit": "毫克" - }, - { - "name": "硒", - "value": 3.75, - "unit": "微克" - }, - { - "name": "硫胺素", - "value": 0.23, - "unit": "毫克" - }, - { - "name": "碘", - "value": 2308.14, - "unit": "微克" - }, - { - "name": "碳水化合物", - "value": 69.96, - "unit": "克" - }, - { - "name": "磷", - "value": 455.9, - "unit": "毫克" - }, - { - "name": "维生素A", - "value": 180.02, - "unit": "微克" - }, - { - "name": "维生素B", - "value": 60.19, - "unit": "毫克" - }, - { - "name": "维生素C", - "value": 0.46, - "unit": "毫克" - }, - { - "name": "维生素E", - "value": 50.13, - "unit": "毫克" - }, - { - "name": "胡萝卜素", - "value": 1079.4, - "unit": "微克" - }, - { - "name": "能量", - "value": 920.1, - "unit": "千卡" - }, - { - "name": "脂肪", - "value": 71.91, - "unit": "克" - }, - { - "name": "膳食纤维", - "value": 38.25, - "unit": "克" - }, - { - "name": "蛋白质", - "value": 27.56, - "unit": "克" - }, - { - "name": "钙", - "value": 755.52, - "unit": "毫克" - }, - { - "name": "钠", - "value": 7833, - "unit": "毫克" - }, - { - "name": "钾", - "value": 624.44, - "unit": "毫克" - }, - { - "name": "铁", - "value": 43.78, - "unit": "毫克" - }, - { - "name": "铜", - "value": 0.66, - "unit": "毫克" - }, - { - "name": "锌", - "value": 15.9, - "unit": "毫克" - }, - { - "name": "锰", - "value": 4.95, - "unit": "毫克" - }, - { - "name": "镁", - "value": 272.2, - "unit": "毫克" - } - ], - "statistics": { - "view_count": 0, - "comment_count": 0, - "like_count": 0, - "recommend_count": 0, - "recommend_score": 0 - }, - "meta": { - "indices": { - "营养": 7, - "难易": 7, - "时间": 6 - }, - "process": "焖", - "taste": "咸鲜味", - "eating_time": [ - "早餐", - "中餐", - "晚餐" - ] - } - }, - "_cached": false, - "_query_time": "1305.78ms" -} \ No newline at end of file +{"code":200,"message":"success","data":{"id":32891,"code":"CP032891","pic_id":7640,"title":"海带焖木耳","intro":"早餐、中餐、晚餐","content":"1.海带洗净,切去梗,切成3厘米见方的块;\n2.海带用沸水焯过捞起;\n3.黑木耳用水发好,剔去杂质,洗净;\n4.葱白切段,姜拍松;\n5.油豆腐切成4厘米见方的块;\n6.炒锅放旺火上,倒入花生油,烧热,煸生姜、葱段,倒入海带、木耳、豆腐,加料酒、酱油、白糖、香醋及适量水,烧30分钟;\n7.调入味精颠翻装盘,淋香油,撒胡椒粉,即成。","cover":"","status":0,"create_time":146085838541,"update_time":146085838541,"category":{"id":42,"name":"家常菜","alias":"","hierarchy":[{"id":11,"name":"菜谱","alias":"caipu","level":3},{"id":12,"name":"中国菜","alias":"","level":2},{"id":42,"name":"家常菜","alias":"","level":1}]},"author":{"id":1,"name":"520kiss","alias":"","email":"null@null.com","homepage":""},"tags":[],"ingredients":{"main":[{"ingredient_id":253537,"name":"海带(鲜}","amount":"250克","sort":0,"detail_id":0,"detail":null}],"auxiliary":[{"ingredient_id":253553,"name":"木耳(干}","amount":"30克","sort":1,"detail_id":0,"detail":null},{"ingredient_id":253568,"name":"油豆腐","amount":"100克","sort":2,"detail_id":364,"detail":{"alias":[],"usage_tip":["一般人皆可食用油豆腐相对于其他豆制品不易消化,经常消化不良、胃肠功能较弱的人慎食。"],"introduction":"油豆腐是豆腐的炸制食品,色泽金黄,易吸收汤汁,常被用做入汤的原料。","nutrition":"油豆腐富含优质蛋白、多种氨基酸、不饱和脂肪酸及磷脂等,铁、钙的含量也很高。","guidance":"炸制油豆腐,火要大,这样才会里嫩外酥。","effect":"","other":"","allergen":["豆腐","豆"],"allergen_type":["豆类"]}}],"seasoning":[{"ingredient_id":253585,"name":"料酒","amount":"25克","sort":3,"detail_id":1208,"detail":{"alias":[],"usage_tip":["一般人群均可食用"],"introduction":"料酒就是专门用于烹饪调味的酒。在我国的应用已有上千年的历史,日本、美国、欧洲的某些国家也有使用料酒的习惯。从理论上来说,啤酒、白酒、黄酒、葡萄酒、威士忌都可用作料酒。但人们经过长期的实践、品尝后发现,不同的料酒所烹饪出来的菜肴风味相距甚远。经过反复试验,人们发现以黄酒烹饪为最佳。酒为人们所喜欢的饮料,品种极多,作调味品的主要是黄酒。福建、山东、浙江等地都有生产,以浙江沼兴所产质量较好。黄酒是用糯米或小米酿造而成的,其成分主要有酒精、糖分、糊精、有机酸类、氨基酸、酯类、醛类、杂醇油及浸出物等。其酒精浓度低,含量在15%以下,而酯类含量高,富含氨基酸,所以香味浓郁,味道醇厚,在烹制菜肴中使用广泛。黄酒的调味作用主要为去腥、增香。","nutrition":"1. 动物性原料作菜肴时,因为肉、脏腑、鱼类等的组织中和鱼类身体表面的粘液里含有腥臊异味,这些物质在加热时能被酒中的酒精所溶解,并随气化的酒精一齐挥发,这样就除去了腥味;\n2. 黄酒中的氨基酸还能与糖结合成芳香醛,产生诱人的香气;\n3. 黄酒中所含的酯类也有香气,所以烹调中加入黄酒,能使菜肴除去异味,且香味大增;\n4. 黄酒中还含有多种维生素和微量元素,而且使菜肴的营养更加丰富;\n5. 在烹饪肉、禽、蛋等菜肴时,调入黄酒能渗透到食物组织内部,溶解微量的有机物质,从而使菜肴质地松嫩;\n6. 温饮黄酒,可帮助血液循环,促进新陈代谢,具有补血养颜,活血祛寒,通经活络,能有效抵御寒冷刺激,预防感冒;\n7. 黄酒还可作为药引子食用。","guidance":"烹调菜肴时不要放得过多,以免料酒味太重而影响菜肴本身的滋味。","effect":"","other":"","allergen":["酒","料酒"],"allergen_type":["调味品类"]}},{"ingredient_id":253604,"name":"酱油","amount":"20克","sort":4,"detail_id":1209,"detail":{"alias":["豉油","酱汁","豉汁"],"usage_tip":["一般人群均可食用"],"introduction":"酱油俗称豉油,主要由大豆,淀粉、小麦、食盐经过制油、发酵等程序酿制而成的。酱油的成分比较复杂,除食盐的成分外,还有多种氨基酸、糖类、有机酸、色素及香料民分。以咸味为主,亦有鲜味、香味等。它能增加和改善菜肴的口味,还能增添或改变菜肴的色泽。我国人民在数千年前就已经掌握酿制工艺了。酱油一般有老抽和生抽两种:老抽较咸,用于提色;生抽用于提鲜。","nutrition":"1. 烹调食品时加入一定量的酱油,可增加食物的香味,并可使其色泽更加好看,从而增进食欲;\n2. 酱油的主要原料是大豆,大豆及其制品因富含硒等矿物质而有防癌的效果;\n3. 酱油含有多种维生素和矿物质,可降低人体胆固醇,降低心血管疾病的发病率,并能减少自由基对人体的损害;\n4. 酱油可用于水、火烫伤和蜂、蚊等虫的蜇伤,并能止痒消肿。","guidance":"1. 要食用“酿造”酱油,而不要吃“配制”酱油;\n2. “餐桌酱油”拌凉菜用,“烹调酱油”未经加热不宜直接食用;\n2. 酱油应在菜肴将要出锅时加入,不宜长时间加热。","effect":"","other":"","allergen":[],"allergen_type":[]}},{"ingredient_id":253620,"name":"味精","amount":"4克","sort":5,"detail_id":1207,"detail":{"alias":["味素","味之素"],"usage_tip":["一般成年人均可食用记忆障碍患者、高血压不宜食用;孕妇及婴幼儿不宜吃味精;老人和儿童也不宜多食。"],"introduction":"味精是烹调中常用的鲜味调味品,有固体味精和液体味精两种。液体味精是未经炼成颗粒的味精原液,饮食业中以用固体味精为常见。味精的化学名称叫谷氨酸钠,由大豆、小麦面粉及其他含蛋白较高的物质,经由淀粉发酵法制成,除含有谷氨酸钠外还含有少量的食盐,以含谷氨酸钠的多少(90%、95%、90%、80%),分成各种规格。全国各地均有生产。","nutrition":"1. 味精对人体没有直接的营养价值,但它能增加食品的鲜味,引起人们食欲,有助于提高人体对食物的消化率;\n2. 味精中的主要成分谷氨酸钠还具有治疗慢性肝炎、肝昏迷、神经衰弱、癫痫病、胃酸缺乏等病的作用。","guidance":"1. 对用高汤烹制的菜肴,不必使用味精,因为高汤本身已具有鲜、香、清的特点,味精则只有一种鲜味,而它的鲜味和高汤的鲜味也不能等同,如使用味精,会将本味掩盖,致使菜肴口味不伦不类;\n2. 对酸性菜肴,如:糖醋、醋熘、醋椒菜类等,不宜使用味精,因为味精在酸性物质中不易溶解,酸性越大溶解度越低,鲜味的效果越差;\n3. 拌凉菜使用晶体味精时,应先用少量热水化开,然后再浇到凉菜上,效果较好,因味精在45℃时才能发挥作用,如果用晶体直接拌凉菜,不易拌均匀,影响味精的提鲜作用;\n4. 作菜使用味精,应在起锅时加入,因为在高温下,味精会分解为焦谷氨酸钠,即脱水谷氨酸钠,不但没有鲜味,而且还会产生轻微的毒素,危害人体;\n5. 味精使用时应掌握好用量,并不是多多益善,它的水稀释度是3000倍,人对味精的味觉感为\n0. 033%,在使用时,以1500倍左右为适宜,如投放量过多,会使菜中产生似成非成,似涩非涩的怪味,造成相反的效果;\n6. 味精在常温下不易溶解,在 70~90度时溶解最好,鲜味最足,超过100度时味精就被水蒸气挥发,超过130度时,即变质为焦谷氨酸钠,不但没有鲜味,还会产生毒性,对炖、烧、煮、熬、蒸的菜,不宜过早放味精,要在将出锅时放入;\n7. 在含有碱性的原料中不宜使用味精,回味精遇碱会化合成谷氨酸二钠,会产生氨水臭味。","effect":"","other":"","allergen":["味精"],"allergen_type":["调味品类"]}},{"ingredient_id":253635,"name":"姜","amount":"4克","sort":6,"detail_id":1,"detail":{"alias":["生姜","黄姜","均姜"],"usage_tip":["1. 适宜伤风感冒、寒性痛经、晕车晕船者食用。","2. 阴虚内热及邪热亢盛者忌食。"],"introduction":"姜属姜科,为植物姜的干燥根茎或鲜根茎,多年生草本植物。原产印度、马来西亚,我国自古栽培,周朝食用。姜供食用的部位为不规则的块茎,呈灰白或黄色,具有辛辣味。姜按用途和收获季节不同而有嫩姜和老姜之分。嫩姜多在八月份挖掘,一般含水多,纤维少,辛辣味淡薄,除做调味品外,尚可炒食,做姜糖等;老姜多在十一月份挖掘,水分少,辛辣味浓,主要用做调味。姜是一种极为重要的调味品,同时也可作为蔬菜单独食用,而且还是一味重要的中药材。它可将自身的辛辣味和特殊芳香渗入到菜肴中,使之鲜美可口,味道清香。","nutrition":"生姜还具有解毒杀菌的作用,日常我们在吃松花蛋或鱼蟹等水产时,通常会放上一些姜末、姜汁。人体在进行正常新陈代谢生理功能时,会产生一种有害物质氧自由基,促使机体发生癌症和衰老。生姜中的姜辣素进入体内后,能产生一种抗氧化本酶,它有很强的对付氧自由基的本领,比维生素E还要强得多。所以,吃姜能抗衰老,老年人常吃生姜可除“老年斑”。生姜的提取物能刺激胃粘膜,引起血管运动中枢及交感神经的反射性兴奋,促进血液循环,振奋胃功能,达到健胃、止痛、发汗、解热的作用。姜的挥发油能增强胃液的分泌和肠壁的蠕动,从而帮助消化;生姜中分离出来的姜烯、姜酮的混合物有明显的止呕吐作用。生姜提取液具有显著抑制皮肤真菌和杀来头阴道滴虫的功效,可治疗各种痈肿疮毒。生姜有抑制癌细胞活性、降低癌的毒害作用。","guidance":"1. 吃饭不香或饭量减少时吃上几片姜或者在菜果放上一点嫩姜,都能改善食欲,增加饭量,所以俗话说:“饭不香,吃生姜”。\n2. 姜可煎汤内服,佐料,入菜炒食,或切片炙穴位。老姜可做调料或配料;嫩姜可用于炒、拌、爆等,如“嫩姜炒牛肉丝”、“嫩姜爆鸭丝”等。\n3. 吃姜一次不宜过多,以免吸收大量姜辣素,在经肾脏排泄过程中会刺激肾脏,并产生口干、咽痛、便秘等“上火”症状。\n4. 烂姜、冻姜不要吃,因为姜变质后会产生致癌物,由于姜性质温热,有解表功效,所以只能在受寒的情况下作为食疗应用。烹调用途:生姜重要的调料品,因为其味清辣,只将食物的异味挥散,而不将食品混成辣味,宜作荤腥菜的矫味品,亦用于糕饼糖果制作,如姜饼、姜糖等。","effect":"生姜味辛、性微温,入脾、胃、肺经;具有发汗解表,温中止呕,温肺止咳,解毒的功效;主治外感风寒、胃寒呕吐、风寒咳嗽、腹痛腹泻、中鱼蟹毒等病症。","other":"","allergen":["姜"],"allergen_type":["蔬菜类"]}},{"ingredient_id":253650,"name":"大葱","amount":"10克","sort":7,"detail_id":2,"detail":{"alias":["葱","青葱","四季葱","事菜"],"usage_tip":["1. 脑力劳动者更宜;","2. 患有胃肠道疾病特别是溃疡病的人不宜多食;另外葱对汗腺刺激作用较强,有腋臭的人在夏季应慎食;表虚、多汗者也应忌食;过多食用葱还会损伤视力。"],"introduction":"葱属百合科,是多年生草本植物葱的茎与叶,上部为青色葱叶,下部为白色葱白。原产于西伯利亚,我国栽培历史悠久,分布广泛,而以山东、河北、河南等省为重要产地。大葱耐寒抗热,适应性强,四季均可上市。普通大葱,原产我国,遍及南北各地。叶圆而中空,叶鞘基部抱合成“假茎”,幼嫩时叶和葱白都能食用。根据葱白的长短又分为两个类型。大葱植株高大,葱白洁白而味甜,在北方栽培较多。葱是日常厨房里的必备之物,北方以大葱为主,它不仅可作调味之品,而且能防治疫病,可谓佳蔬良药。大葱多用于煎炒烹炸;南方多产小葱,是一种常用调料,又叫香葱,一般都是生食或拌凉菜用。","nutrition":"1. 生葱像洋葱、大葱一样,含烯丙基硫醚。而烯丙基硫醚会刺激胃液的分泌,且有助于食欲的增进。同时与维生素B1含量较多的食物一起摄取时,维生素B1所含的淀粉及糖质会变为热量,而提高恢复疲劳的作用。\n2. 葱叶部分要比葱白部分含有更多的维生素A、维C及钙。葱中含有相当量的维生素C,有舒张小血管,促进血液循环的作用,有助于防止血压升高所致的头晕,使大脑保持灵活和预防老年痴呆的作用。\n3. 经常吃葱的人,即便脂多体胖,但胆固醇并不增高,而且体质强壮。葱含有微量元素硒,并可降低胃液内的亚硝酸盐含量,对预防胃癌及多种癌症有一定作用。\n4. 葱含有具有刺激性气味的挥发油和辣素,能祛除腥腥膻等油腻厚味菜肴中的异味,产生特殊香气,并有较强的杀菌作用,可以刺激消化液的分泌,增进食欲。挥发性辣素还通过汗腺、呼吸道、泌尿系统排出时能轻微刺激刺激相关腺体的分泌,而起到发汗、祛痰、利尿作用。是治疗感冒的中药之一。\n5. 葱还有降血脂、降血压、降血糖的作用,如果与蘑菇同食可以起到促进血液循环的作用。","guidance":"1. 每天食用葱,对身体有益。葱可生吃,也可凉拌当小菜食用,作为调料,多用于荤、腥、膻、以及其他有异味的菜肴、汤羹中,对没有异味的菜肴、汤羹也起增味增香作用。\n2. 根据主料的不同,可切成葱段和葱末掺合使用,均不宜煎、炸过久。\n3. 葱叶因富含维生素A原,不应轻易丢弃不用。\n4. 葱中含有的烯丙基硫醚由于是属于挥发性,因此泡在水里或煮得过久,都会使其效果丧失。\n5. 在加入味增汁熄火之后,再洒上葱花,即可使香味更可口,且可发挥烯丙基硫醚的效果。\n6. 葱与维生素B1含量较多的食品一起摄取。因为具有消除臭味的作用,因此像猪肉或羊肉等带有腥味的菜肴务必要使用葱来调味。","effect":"葱味辛、性温;能通阳活血、驱虫解毒、发汗解表;主治风寒感冒轻症、痈肿疮毒、痢疾脉微、寒凝腹痛、小便不利等病症。对感冒、风寒、头痛、阴寒腹痛、虫积内阻、痢疾等有较好的治疗作用。","other":"","allergen":["葱"],"allergen_type":["蔬菜类"]}},{"ingredient_id":253665,"name":"醋","amount":"10克","sort":8,"detail_id":1211,"detail":{"alias":["苦酒","淳酢","醯","酢"],"usage_tip":["一般人群均可食用脾胃湿盛、外感初起者忌服;胃溃疡和胃酸过多者不宜食醋。"],"introduction":"醋是一种发酵的酸味液态调味品,以含淀粉类的粮食(高粱、黄米、糯米、籼米等)为主料,谷糠、稻皮等为辅料,经过发酵酿造而成。醋在烹调中为主要的调味品之一,以酸味为主,且有芳香味,用途较广,是糖醋味的主要原料。它能去腥解腻,增加鲜味和香味,能在食物加热过程中使维生素C减少损失,还可使烹饪原料中钙质溶解而利于人体吸收。比较著名的品种有江苏镇江的香醋和山西的老陈醋等,常用于溜菜、拌菜及腥味较重的菜肴中。食醋因原料和制作方法的不同,可分为发酵醋和人工合成醋两种,其品种主要有米醋、熏醋、白醋等。米醋主要原料为高粱、黄米、麸皮、米糠、盐,经醋曲发酵后制成,呈浅棕色,香味浓郁,质量较好,适合于蘸食和炒菜;熏醋原料除无黄米外,基本与米醋原料相同,发酵后略加花椒、桂皮等熏制而成,颜色较深,以存放时间长者为好,适合于蘸食和炒菜;白醋(又称醋精)为冰醋酸加水稀释而成,醋酸的含量高于米醋等,酸味大,无香味。浓醋酸有一定的腐蚀作用,使用时应根据需要稀释和控制用量。烹调菜肴时加点醋,不仅使菜肴脆嫩可口,祛除腥膻味,还能保护其中的营养素。但是正在服用某些药物如:磺胺类药、碱性药、抗生素、解表发汗的中药的人不宜食醋。","nutrition":"1. 醋可以开胃,促进唾液和胃液的分泌,帮助消化吸收,使食欲旺盛,消食化积;\n2. 醋有很好的抑菌和杀菌作用,能有效预防肠道疾病、流行性感冒和呼吸疾病;\n3. 醋可软化血管、降低胆固醇,是高血压等心脑血管病人的一剂良方;\n4. 醋对皮肤、头发能起到很好的保护作用,中国古代医学就有用醋入药的记载,认为它有生发、美容、降压、减肥的功效;\n5. 醋可以消除疲劳,促进睡眠,并能减轻晕车、晕船的不适症状;\n6. 醋还能减少胃肠道和血液中的酒精浓度,起到醒酒的作用;\n7. 醋还有使鸡骨、鱼翅软化,促进钙吸收的作用。","guidance":"1. 吃饺子蘸醋或食用醋较多的菜肴后应及时漱口以保护牙齿;\n2. 作菜时,加醋的最佳时间是在两头,即原料入锅后马上加醋及菜肴临出锅前加醋,第一次应多些,第二次应少些;\n3. 醋可以用于需要去腥解腻的原料,如烹制水产品或肚、肠、心等,可消除腥臭和异味,对一些腥臭较重的原料还可以提前用醋浸渍;\n4. 醋用于烹制带骨的原料,如排骨、鱼类等,可使骨刺软化,促进骨中的矿物质如钙、磷溶出,增加营养成分。","effect":"醋味酸苦、性温,入肝、胃经;有散瘀,止血,解毒,杀虫的功效;主治产后血晕、黄疸、黄汗、吐血、衄血、大便下血、痈疽疮肿,又可解鱼肉菜毒。","other":"中国古代酸味调味应用较多,醋传为造酒时所创制,《四民月令》已载有作醋方法,至北魏《齐民要术》,其中制醋法已达20余种。以后各代均有名醋出现。至今醋仍为开门七件事之一,在生活中占有重要地位。","allergen":[],"allergen_type":[]}},{"ingredient_id":253678,"name":"白砂糖","amount":"10克","sort":9,"detail_id":769,"detail":{"alias":["砂糖","石蜜","白霜糖","白糖"],"usage_tip":["一般人群均可食用"],"introduction":"糖是用甘蔗或甜菜等植物加工而成的一种调味品,其主要成分是蔗糖。白砂糖是食糖中质量最好的一种。其颗粒为结晶状,均匀,颜色洁白,甜味纯正,甜度稍低于红糖。烹调中常用。绵白糖为粉末状,适合于烹调之用,甜度与白砂糖差不多。绵白糖有精制绵白糖和土法制的绵白糖两种。前者色泽洁白,晶粒细软,质量较好;后者色泽微黄稍暗,质量较差。白砂糖和绵白糖只是结晶体大小不同,白砂糖的结晶颗粒大,含水分很少,而绵白糖的结晶颗粒小,含水分较多。广东、福建、台湾等省和东北地区是我国主要产糖区。糖是重要的调味品,能增加菜肴的甜味及鲜味,增添制品的色泽,为制作菜肴特别是甜菜品种的主要调味原料。","nutrition":"1. 适当食用白糖有助于提高机体对钙的吸收,但过多就会妨碍钙的吸收;\n2. 吃糖后应及时漱口或刷牙,以防龋齿的产生;3 .糖尿病病人不易直接食用食糖,最好是以甜味剂替代。","guidance":"1. 炒菜时不小心把盐放多了,加入适量白糖,就可解咸;\n2. 糖很容易生螨,存放日久的糖不要生吃,应煮开后食用。","effect":"白砂糖味甘、性平,归脾、肺经;有润肺生津、止咳、和中益肺、舒缓肝气、滋阴、调味、除口臭、解盐卤毒之功效。","other":"制糖为我国首创,早在三千多年前我国就有用谷物制作饴糖的记载。根据《齐民要术》的记载可知后汉时我国已经生产蔗糖和冰糖了。唐贞观年间我国自印度传入熬糖法后,改进了工艺,蔗糖质量有所提高。","allergen":[],"allergen_type":[]}},{"ingredient_id":253690,"name":"香油","amount":"5克","sort":10,"detail_id":846,"detail":{"alias":["麻油","芝麻油"],"usage_tip":["老少皆宜"],"introduction":"芝麻油Sesame oil,简称麻油,俗称香油,是小磨香油和机制香油的统称,亦即具有浓郁或显著香味的芝麻油。在加工过程中,芝麻中的特有成分经高温炒料处理后,生成具有特殊香味的物质,致使芝麻油具有独特的香味,有别于其它各种食用油,故称香油。按加工工艺不同,香尚未分为小磨香油和机制香油两种。芝麻(Sesamum indicum)可能原产于非洲一带,产出的油用于烹饪并加在沙拉里,在中菜里也很受欢迎。","nutrition":"1. 延缓衰老:香油中含丰富的维生素E,具有促进细胞分裂和延缓衰老的功能;\n2. 保护血管:香油中含有40%左右的亚油酸、棕榈酸等不饱和脂肪酸,容易被人体分解吸收和利用,以促进胆固醇的代谢,并有助于消除动脉血管壁上的沉积物;芝麻油是一种促凝血药,用于治疗血小板减少性紫癜和出血性素质有一定效果;\n3. 润肠通便;\n4. 减轻烟酒毒害:有抽烟习惯和嗜酒的人经常喝点香油,可以减轻烟对牙齿、牙龈、口腔黏膜的直接刺激和损伤,以及肺部烟斑的形成,同时对尼古丁的吸收也有相对的抑制作用。饮酒之前喝点香油,则对口腔、食道、胃贲门和胃黏膜起到一定的保护作用;\n5. 保护嗓子:常喝香油能增强声带弹性,使声门张合灵活有力,对声音嘶哑、慢性咽喉炎有良好的恢复作用;\n6. 从芝麻中榨出香油中所含的卵磷脂都是益寿延年抗衰老的上佳成分,是中老年人最好的冬令补品;","guidance":"","effect":"芝麻油有利于食物的消化吸收,有延缓衰老、保护血管、润肠通便、减轻烟酒毒害、保护嗓子的功效;对口腔溃疡、牙周炎、牙龈出血、咽喉发炎均有很好的改善作用。","other":"","allergen":[],"allergen_type":[]}},{"ingredient_id":253702,"name":"胡椒粉","amount":"2克","sort":11,"detail_id":1210,"detail":{"alias":[],"usage_tip":["一般人群均可食用消化道溃疡、咳嗽咯血、痔疮、咽喉炎症、眼疾患者慎食。"],"introduction":"胡椒为热带植物胡椒树的果实,主要产在印度、越南、印尼、泰国、新加坡等国,我国广东省海南岛也有生产。胡椒味辛辣芳香,性热,除可去腥增香外,还有除寒气、消积食的效用,但多食则刺激胃粘膜而引起充血。胡椒粉是用干胡椒碾压而成,有白胡椒粉和黑胡椒粉两种。黑胡椒粉是未成熟果实加工而成,白胡椒粉是果实完全成熟后采摘加工而成。","nutrition":"1. 胡椒的主要成分是胡椒碱,也含有一定量的芳香油、粗蛋白、粗脂肪及可溶性氮,能祛腥、解油腻,助消化;\n2. 胡椒的气味能增进食欲;\n3. 胡椒性温热,对胃寒所致的胃腹冷痛、肠鸣腹泻有很好的缓解作用,并治疗风寒感冒;\n4. 胡椒有防腐抑菌的作用,可解鱼虾肉毒;\n5. 黑胡椒的辣味比白胡椒强烈,香中带辣,祛腥提味,更多的用于烹制内脏、海鲜类菜肴;\n6. 白胡椒的药用价值较大,可散寒、健胃等,可以增进食欲、助消化,促发汗;还可以改善女性白带异常及癫痫症。","guidance":"","effect":"胡椒味辛、性热,入胃、大肠经;有温中下气,消痰解毒的功效;主治寒痰食积、脘腹冷痛、反胃、呕吐清水、泄泻、冷痢;外敷治疮肿、毒蛇咬伤、犬咬伤;又可解食物毒。1.温中散寒:用于胃寒所致的胃脘痛、呕吐、以及腹冷所致的泄泻、肠鸣;2.醒脾开胃:本品小剂量能增进食欲,对胃口差、消化不良有治疗作用。","other":"胡椒始见载于唐代《酉阳杂俎》《唐本草》诸书,传为唐僧西域取经携回。以后历代本草均有记述,多供药用,亦用于食品调味。","allergen":["胡椒"],"allergen_type":["调味品类"]}},{"ingredient_id":253710,"name":"花生油","amount":"30克","sort":12,"detail_id":849,"detail":{"alias":["落花生油","果油"],"usage_tip":["适合所有人,特别是中老年人食用。"],"introduction":"花生油Peanut oil,为豆科植物花生的种子榨出之脂肪油,淡黄透明,色泽清亮,气味芬芳,滋味可口,是一种比较容易消化的食用油。可提供给人体大量营养,含多种脂肪酸的甘油酯,可增加食品的美味,是构成人体内多种组织成分的重要原料。是目前我国主要的食用植物油之一,可用于炒、煎、炸各种菜肴和食品。花生的油脂含量约有50%,很适合拌沙拉或作为炸油。也用来制植物奶油或鱼罐头。","nutrition":"1. 中国预防医学科学院经研究证实,花生油含锌量是色拉油、粟米油、菜籽油、豆油的许多倍。虽然补锌的途径很多,但油脂是人们日常必需的补充物,所以食用花生油特别适宜于大众补锌;\n2. 花生油中还含有多种抗衰老成分,有延缓脑功能衰老的作用。花生油还具有健脾润肺,解积食、驱脏虫的功效;\n3. 营养专家还在花生油中发理了3种有益寿延年于心脑血管的保健成分;白藜芦醇、单不饱和脂肪酸和β-谷固醇,实验证明,这几种物质是肿瘤类疾病的化学预防剂,也是降低血小板聚集、防治动脉硬化及心脑血管疾病的化学预防剂;\n4. 是中老年人理想的食用油脂之一,花生油中的胆碱,还可改善人脑的记忆力,延缓脑功能衰退。","guidance":"1. 花生油热量高,脂肪量大,不宜过量食用,否则对心脑血管还是会有一定影响,而且容易发胖。\n2. 花生油油耐高温,除炒菜外适合于煎炸食物。\n3. 用花生油炒菜,在油加热后,先放盐,在油中爆约30秒,可除去花生油中可能存在的黄曲霉素。\n4. 植物油可防止粥沫:煮稀饭时,往锅里滴几滴花生油,并改用文火,稀饭就不会有沫子外溢了。","effect":"花生油味甘、性平,入脾、肺、大肠经;可补脾润肺、润肠下虫;花生油熟食,有润肠逐虫之功效,可治疗蛔虫性肠梗阻。","other":"","allergen":["花生"],"allergen_type":["坚果类"]}}]},"allergens":["豆腐","豆","酒","料酒","味精","姜","葱","胡椒","花生"],"nutrition":[{"name":"叶酸","value":13.98,"unit":"微克"},{"name":"核黄素","value":0.28,"unit":"毫克"},{"name":"烟酸","value":5.66,"unit":"毫克"},{"name":"硒","value":3.75,"unit":"微克"},{"name":"硫胺素","value":0.23,"unit":"毫克"},{"name":"碘","value":2308.14,"unit":"微克"},{"name":"碳水化合物","value":69.96,"unit":"克"},{"name":"磷","value":455.9,"unit":"毫克"},{"name":"维生素A","value":180.02,"unit":"微克"},{"name":"维生素B","value":60.19,"unit":"毫克"},{"name":"维生素C","value":0.46,"unit":"毫克"},{"name":"维生素E","value":50.13,"unit":"毫克"},{"name":"胡萝卜素","value":1079.4,"unit":"微克"},{"name":"能量","value":920.1,"unit":"千卡"},{"name":"脂肪","value":71.91,"unit":"克"},{"name":"膳食纤维","value":38.25,"unit":"克"},{"name":"蛋白质","value":27.56,"unit":"克"},{"name":"钙","value":755.52,"unit":"毫克"},{"name":"钠","value":7833,"unit":"毫克"},{"name":"钾","value":624.44,"unit":"毫克"},{"name":"铁","value":43.78,"unit":"毫克"},{"name":"铜","value":0.66,"unit":"毫克"},{"name":"锌","value":15.9,"unit":"毫克"},{"name":"锰","value":4.95,"unit":"毫克"},{"name":"镁","value":272.2,"unit":"毫克"}],"statistics":{"view_count":0,"comment_count":0,"like_count":0,"recommend_count":0,"recommend_score":0},"meta":{"indices":{"营养":7,"难易":7,"时间":6},"process":"焖","taste":"咸鲜味","eating_time":["早餐","中餐","晚餐"]}},"_cached":true,"_cache_age":394,"_query_time":"1292.3ms"} +即刻翻译 + + + + + diff --git a/docs/audit/OPTIMIZATION_PLAN.md b/docs/audit/OPTIMIZATION_PLAN.md deleted file mode 100644 index 37d307e..0000000 --- a/docs/audit/OPTIMIZATION_PLAN.md +++ /dev/null @@ -1,130 +0,0 @@ -/** - * 优化方案文档 - * 创建时间: 2026-04-09 - * 更新时间: 2026-04-09 - * 名称: 优化方案 - * 作用: 整合审计文档中的问题和解决方案,提供清晰的优化步骤 - * 上次更新内容: 删除已完成的任务,仅保留未解决的问题 - */ - -# 优化方案与执行步骤 - -更新日期: 2026-04-09 - -## 概述 - -本文档整合了 `api_mapping_report.md`、`development_tasks.md`、`project_issues_and_plan.md` 和 `CACHE_STRATEGY.md` 中的问题和解决方案,提供清晰的优化步骤和可执行计划。 -- 已完成的任务已移除,仅保留未解决的问题。 - ---- - -## 优先级 3(规划内)🎯 - -### 1. UI 风格统一(iOS/Cupertino 优先) - -**问题描述** -- 项目要求优先 iOS/Cupertino 风格(见 AGENTS.md) -- 存在多处风格差异 -- 颜色、圆角、按钮显示不统一 - -**影响文件** -- 全仓库 UI 相关文件 - -**解决方案** - -#### 统一风格 -- 建立主题变量(颜色、圆角、字体、间距) -- 创建 Cupertino 优先的组件库 -- 重构常用组件统一样式 -- 运行样式审查 - -**执行步骤** -1. 创建 `lib/src/theme/` 目录 -2. 定义主题变量(颜色、圆角、字体、间距) -3. 创建 Cupertino 风格组件库 -4. 按页面分批重构 -5. 运行样式审查 - -**预计工时**:3-5 人日(按页面分批推进) - ---- - -### 2. 增加集成/端到端验证脚本 - -**问题描述** -- 缺少 API 验证脚本 -- 缺少前端关键路径的集成/端到端测试 - -**影响文件** -- `docs/audit/responses/`(已有样例) - -**解决方案** - -#### 验证脚本 -- 补充 API 验证脚本(基于已保存样例) -- 在 CI/本地提供运行步骤 -- 补充前端关键路径测试 - -**执行步骤** -1. 创建 API 验证脚本目录 -2. 基于已有样例编写验证脚本 -3. 添加到 CI 流程 -4. 补充前端集成测试 - -**预计工时**:1-2 人日 - ---- - -### 3. API 相关问题 - -**问题描述** -- `api_preference.php` 的 `get` 返回结构需运行时确认样例 -- `api_what_to_eat.php` 在 `detail`/`random` 的返回 key 变化在部分页面上存在解析差异 -- `api_action.php` 需改造为 POST 并在前端处理 429 优雅降级 -- 偏好格式(后端对象 vs 前端 ID)需要团队决策 - -**影响文件** -- `lib/src/repositories/action_repository.dart` -- `lib/src/repositories/preference_repository.dart` -- `lib/src/repositories/what_to_eat_repository.dart` - -**解决方案** -1. 召开快速决策会确认偏好数据方案与写接口迁移计划 -2. 实现对 `ActionRepository` 的 429 友好处理与前端 `user_id` 持久化 -3. 运行端到端请求验证 API 返回结构 - -**执行步骤** -1. 召开 30 分钟的快速决策会 -2. 基于决策实现前端改造或后端补丁 -3. 实现 `ActionRepository` 的 429 退避处理 -4. 运行 API 验证脚本验证返回结构 - -**预计工时**:1-2 人日 - ---- - -### 4. 缓存策略待实现功能 - -**问题描述** -- 实现 `X-Invalidate` 响应头支持 -- 实现缓存统计和监控 -- 实现缓存清理功能 -- 实现缓存预热功能 - -**影响文件** -- `lib/src/services/api/api_service.dart` -- `lib/src/models/api/api_response.dart` - -**解决方案** -1. 实现 `X-Invalidate` 响应头支持 -2. 添加缓存统计和监控功能 -3. 实现缓存清理功能 -4. 实现缓存预热功能 - -**执行步骤** -1. 修改 `api_service.dart` 添加 `X-Invalidate` 响应头支持 -2. 实现缓存统计和监控功能 -3. 实现缓存清理功能 -4. 实现缓存预热功能 - -**预计工时**:2-3 人日 diff --git a/docs/audit/crash_risk_analysis.md b/docs/audit/crash_risk_analysis.md new file mode 100644 index 0000000..af3e3e1 --- /dev/null +++ b/docs/audit/crash_risk_analysis.md @@ -0,0 +1,381 @@ +# 代码闪退风险分析报告 + +> 分析日期: 2026-04-11 +> 修复日期: 2026-04-11 +> 分析范围: 全项目代码审查 +> 严重程度: 🔴 高危 🟡 中危 🟢 低危 + +--- + +## 修复进度 + +### ✅ 已修复 (P0) +- [x] WeeklyMenuController 全局注册 +- [x] BedtimeReminderController 全局注册 +- [x] 移除页面中的重复 Get.put 调用 +- [x] HiveService 添加 box 缓存机制 +- [x] BedtimeReminderController 添加时间有效性检查 + +### ⏸️ 待修复 (P2) +- [ ] 本地通知功能实现(需添加 flutter_local_notifications 依赖) + +--- + +## 一、Controller 重复注册风险 🔴 高危 + +### 问题描述 +多个页面使用 `Get.put()` 直接注册控制器,可能导致重复注册或生命周期混乱。 + +### 风险代码位置 + +#### 1. WeeklyMenuController 重复注册 +**文件**: `lib/src/pages/tools/weekly_menu_planner_page.dart:26` +```dart +final WeeklyMenuController _controller = Get.put(WeeklyMenuController()); +``` +**风险**: +- 该控制器未在 `AppBinding` 中全局注册 +- 每次进入页面都会重新创建实例 +- 如果页面被多次推入,可能导致多个实例共存 +- Hive 数据可能被不同实例覆盖 + +**建议修复**: +```dart +// 方案1: 在 AppBinding 中全局注册 +// lib/src/app_binding.dart +Get.put(WeeklyMenuController(), permanent: true); + +// 方案2: 使用 Get.lazyPut + fenix +class WeeklyMenuBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => WeeklyMenuController(), fenix: true); + } +} +``` + +#### 2. BedtimeReminderController 重复注册 +**文件**: `lib/src/pages/profile/bedtime_reminder_page.dart:19` +```dart +final controller = Get.put(BedtimeReminderController()); +``` +**风险**: 同上 + +**建议修复**: +```dart +// 在 AppBinding 中全局注册 +// lib/src/app_binding.dart +Get.put(BedtimeReminderController(), permanent: true); +``` + +#### 3. MealRecordController 重复注册 +**文件**: `lib/src/pages/home/home_page.dart:53` +```dart +Get.put(MealRecordController(), permanent: true); +``` +**风险**: +- 该控制器已在 `NutritionBinding` 中注册(带检查) +- 这里再次注册可能导致重复实例 + +**建议修复**: 移除 home_page.dart 中的注册,依赖 NutritionBinding 的注册逻辑 + +--- + +## 二、HiveService 通用方法风险 🔴 高危 + +### 问题描述 +新添加的通用 `get()` 和 `put()` 方法直接使用 `Hive.box(boxName)` 打开 box,缺少安全检查。 + +### 风险代码位置 +**文件**: `lib/src/services/data/hive_service.dart:449-473` + +```dart +dynamic get(String boxName, String key) { + if (!_initialized) return null; + try { + final box = Hive.box(boxName); // ⚠️ box 可能不存在 + return box.get(key); + } catch (e) { + LoggerService().error('HiveService get error ($boxName/$key): $e'); + return null; + } +} +``` + +**风险**: +- 如果 box 不存在,`Hive.box(boxName)` 会抛出异常 +- 虽然有 try-catch,但每次访问都会尝试打开 box,性能差 +- 多个并发访问可能导致 box 重复打开 + +**建议修复**: +```dart +// 添加 box 缓存机制 +final Map> _dynamicBoxCache = {}; + +Box _getOrOpenBox(String boxName) { + if (_dynamicBoxCache.containsKey(boxName)) { + return _dynamicBoxCache[boxName]!; + } + + try { + final box = Hive.box(boxName); + _dynamicBoxCache[boxName] = box; + return box; + } catch (e) { + LoggerService().error('Failed to open box $boxName: $e'); + rethrow; + } +} + +dynamic get(String boxName, String key) { + if (!_initialized) return null; + try { + final box = _getOrOpenBox(boxName); + return box.get(key); + } catch (e) { + LoggerService().error('HiveService get error ($boxName/$key): $e'); + return null; + } +} + +// 在 close() 方法中清理缓存 +Future close() async { + // ... 现有代码 ... + for (final box in _dynamicBoxCache.values) { + await box.close(); + } + _dynamicBoxCache.clear(); + _initialized = false; +} +``` + +--- + +## 三、空指针风险 🟡 中危 + +### 问题描述 +多处代码缺少空值检查,可能导致空指针异常。 + +### 风险代码位置 + +#### 1. WeeklyMenuController 数据访问 +**文件**: `lib/src/controllers/weekly_menu_controller.dart:111-114` +```dart +void addMealToDay(String dateKey, String mealType, RecipeModel recipe) { + if (currentMenu.value == null) return; + + final dayMenu = currentMenu.value!.dailyMenus[dateKey]; + if (dayMenu == null) return; // ✅ 有检查 + // ... +} +``` +**评价**: 此处有检查,但其他类似方法可能遗漏 + +#### 2. BedtimeReminderController 时间计算 +**文件**: `lib/src/controllers/bedtime_reminder_controller.dart:169-187` +```dart +bool shouldShowBeforeSleepEatingWarning() { + if (!beforeSleepEatingReminder.value) return false; + + final now = DateTime.now(); + final dinnerTime = DateTime( + now.year, + now.month, + now.day, + dinnerHour.value, + dinnerMinute.value, + ); + // ⚠️ 如果 dinnerHour/dinnerMinute 超出有效范围(如 25:70),DateTime 构造会抛异常 + // ... +} +``` +**建议修复**: +```dart +bool shouldShowBeforeSleepEatingWarning() { + if (!beforeSleepEatingReminder.value) return false; + + // 添加时间有效性检查 + if (dinnerHour.value < 0 || dinnerHour.value > 23 || + dinnerMinute.value < 0 || dinnerMinute.value > 59) { + debugPrint('Invalid dinner time: ${dinnerHour.value}:${dinnerMinute.value}'); + return false; + } + + final now = DateTime.now(); + final dinnerTime = DateTime( + now.year, + now.month, + now.day, + dinnerHour.value, + dinnerMinute.value, + ); + // ... +} +``` + +--- + +## 四、TODO 未完成功能 🟡 中危 + +### 问题描述 +代码中存在 TODO 标记的功能未实现,可能导致功能不完整。 + +### 未完成功能 + +#### 1. 本地通知功能 +**文件**: `lib/src/controllers/bedtime_reminder_controller.dart:126-129` +```dart +if (enabled) { + ToastService.show(message: '已启用就寝提醒 🔔'); + // TODO: 实际设置本地通知 +} else { + ToastService.show(message: '已关闭就寝提醒 🔕'); + // TODO: 取消本地通知 +} +``` +**影响**: +- 用户启用提醒后不会收到实际通知 +- 用户体验不完整 + +**建议实现**: +```dart +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class BedtimeReminderController extends BaseController { + final FlutterLocalNotificationsPlugin _notifications = + FlutterLocalNotificationsPlugin(); + + @override + void onInit() { + super.onInit(); + _initNotifications(); + _loadSettings(); + _calculateRecommendedBedtime(); + } + + Future _initNotifications() async { + const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const iosSettings = DarwinInitializationSettings(); + const settings = InitializationSettings( + android: androidSettings, + iOS: iosSettings, + ); + await _notifications.initialize(settings); + } + + Future _scheduleNotification() async { + final now = DateTime.now(); + final bedtime = DateTime( + now.year, + now.month, + now.day, + recommendedBedtimeHour.value, + recommendedBedtimeMinute.value, + ); + + if (bedtime.isBefore(now)) { + bedtime = bedtime.add(Duration(days: 1)); + } + + await _notifications.zonedSchedule( + 'bedtime_reminder', + '就寝提醒', + controller.bedtimeRecommendation, + tz.TZDateTime.from(bedtime, tz.local), + const NotificationDetails( + android: AndroidNotificationDetails( + 'bedtime_reminder_channel', + '就寝提醒', + importance: Importance.high, + ), + iOS: DarwinNotificationDetails(), + ), + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + ); + } + + void toggleReminder(bool enabled) { + reminderEnabled.value = enabled; + _saveSettings(); + + if (enabled) { + ToastService.show(message: '已启用就寝提醒 🔔'); + _scheduleNotification(); + } else { + ToastService.show(message: '已关闭就寝提醒 🔕'); + _notifications.cancel('bedtime_reminder'); + } + } +} +``` + +--- + +## 五、代码不足与改进建议 🟢 低危 + +### 1. 缺少输入验证 + +**问题**: 用户输入的时间、数值等缺少范围验证 + +**建议**: +- 在 Controller 中添加数据验证方法 +- 在 UI 层添加输入限制(如 CupertinoPicker 的范围) +- 添加错误提示 + +### 2. 缺少日志记录 + +**问题**: 关键操作缺少日志记录,不利于问题排查 + +**建议**: +- 在数据保存/加载时添加日志 +- 在异常处理时记录详细错误信息 +- 使用 LoggerService 替代 debugPrint + +### 3. 缺少数据迁移机制 + +**问题**: Hive 数据结构变更时缺少迁移机制 + +**建议**: +- 为每个 box 添加 schema 版本号 +- 实现数据迁移逻辑 +- 参考现有的 `_runMigrations()` 方法扩展 + +### 4. 缺少单元测试 + +**问题**: 关键业务逻辑缺少单元测试 + +**建议**: +- 为 Controller 添加单元测试 +- 为 Service 添加单元测试 +- 为工具类添加单元测试 + +--- + +## 六、优先级修复建议 + +### 🔴 立即修复(P0) +1. **Controller 重复注册问题** - 可能导致数据丢失和内存泄漏 +2. **HiveService 通用方法风险** - 可能导致数据读写失败 + +### 🟡 尽快修复(P1) +3. **空指针风险** - 添加时间有效性检查 +4. **本地通知功能实现** - 完善用户体验 + +### 🟢 计划修复(P2) +5. **输入验证** - 提升代码健壮性 +6. **日志记录** - 便于问题排查 +7. **单元测试** - 保证代码质量 + +--- + +## 七、总结 + +本次代码审查发现的主要问题: + +1. **Controller 管理混乱** - 多处重复注册,需要统一管理 +2. **HiveService 安全性不足** - 通用方法缺少 box 缓存和错误处理 +3. **功能不完整** - 本地通知等 TODO 未实现 +4. **缺少防御性编程** - 输入验证、空值检查不足 + +建议优先修复 P0 和 P1 级别问题,然后逐步完善 P2 级别改进。 diff --git a/docs/dev/UNFINISHED_FEATURES.md b/docs/dev/UNFINISHED_FEATURES.md index 0d551fa..2bba95e 100644 --- a/docs/dev/UNFINISHED_FEATURES.md +++ b/docs/dev/UNFINISHED_FEATURES.md @@ -15,26 +15,28 @@ |------|--------|--------|--------|--------| | 三:热量追踪+营养分析 | 7 | 7 | 0 | 100% ✅ | | 四:购物清单 | 5 | 5 | 0 | 100% ✅ | -| 十三:AI+规划高级功能 | 4 | 0 | 4 | 0% 🔵 | +| 十三:AI+规划高级功能 | 4 | 4 | 0 | 100% ✅ | | 十四:接口能力挖掘 | 8 | 0 | 8 | 0% 🟢 | | 十五:后端接口增强 | 6 | 0 | 6 | 0% 🔴 | | 十六:用户体验优化+Bug 修复 | 7 | 7 | 0 | 100% ✅ | | 十七:紧急Bug修复 | 14 | 14 | 0 | 100% ✅ | -| 十九:综合Bug修复+功能增强 | 18 | 16 | 2 | 89% 🔄 | -| **合计** | **142** | **111** | **31** | **78%** | +| 十九:综合Bug修复+功能增强 | 18 | 18 | 0 | 100% ✅ | +| 二十:用户体验优化+交互增强 | 7 | 7 | 0 | 100% ✅ | +| 二十一:菜谱详情页功能增强 | 1 | 1 | 0 | 100% ✅ | +| **二十二:Picid功能Bug修复** | **2** | **2** | **0** | **100% ✅** | +| **合计** | **152** | **136** | **16** | **89%** | --- ## 五、开发阶段 ### 阶段一:基础设施(P1)✅ 已完成 + **目标**:搭建 Hive 本地数据库 + 数据模型 + 持久化收藏 - - ## 六、页面导航规划 -``` +```text 发现页 ## 八、开发优先级矩阵 @@ -131,29 +133,7 @@ - 热门搜索词从 API 获取 - **技术方案**:`RecipeRepository.fetchTags()` 获取热词 -#### 12.4 拍照记录 -- **入口**:烹饪笔记 → 拍照按钮 -- **功能**: - - 调用相机拍照或从相册选择 - - 图片压缩后保存到本地 - - 笔记列表展示缩略图 -- **技术方案**:`image_picker` + 本地文件存储 -### 需引入的外部依赖 - -| 依赖 | 用途 | 纯Dart | 鸿蒙兼容 | -|------|------|--------|---------| -| `share_plus` | 系统分享 | ❌ | ⚠️ 需适配 | -| `image_picker` | 拍照/相册 | ❌ | ⚠️ 需适配 | -| `screenshot` | 截图 | ✅ | ✅ | - -### 验收标准 -- [ ] 详情页可分享菜谱卡片到其他应用 -- [ ] 烹饪计时器完成时发送本地通知 -- [ ] 搜索页展示热门搜索词 -- [ ] 烹饪笔记可添加照片 - ---- ## 🔵 阶段十三:AI+规划高级功能(P3) @@ -163,10 +143,10 @@ | 序号 | 任务 | 产出文件 | 优先级 | 状态 | 说明 | |------|------|---------|--------|------|------| -| 13.1 | 🤖 AI 菜谱推荐 | `lib/src/services/ai_recommend_service.dart` | P3 | ❌ 未实现 | 基于口味偏好+浏览历史,智能推荐菜谱 | -| 13.2 | 📅 每周菜单规划 | `lib/src/pages/tools/meal_planner_page.dart` | P3 | ❌ 未实现 | 日历视图规划一周饮食,自动生成购物清单 | -| 13.3 | 🧮 食材用量换算增强 | `lib/src/pages/tools/serving_scaler_page.dart` | P3 | ✅ 已完成 | 添加单位换算Tab(重量/容量/计数)+常用换算表 | -| 13.4 | 🌙 就寝提醒 | `lib/src/pages/settings/health_reminder_page.dart` | P3 | ❌ 未实现 | 根据饮食时间推荐健康作息 | +| 13.1 | AI 菜谱推荐 | `lib/src/services/recommendation_service.dart` | P3 | ✅ 已实现 | 基于口味偏好+浏览历史,智能推荐菜谱 | +| 13.2 | 每周菜单规划 | `lib/src/pages/tools/weekly_menu_planner_page.dart` | P3 | ✅ 已实现 | 日历视图规划一周饮食,自动生成购物清单 | +| 13.3 | 食材用量换算增强 | `lib/src/pages/tools/serving_scaler_page.dart` | P3 | ✅ 已实现 | 添加单位换算Tab(重量/容量/计数)+常用换算表 | +| 13.4 | 就寝提醒 | `lib/src/pages/profile/bedtime_reminder_page.dart` | P3 | ✅ 已实现 | 根据饮食时间推荐健康作息 | ### 功能详情 @@ -205,154 +185,19 @@ - **技术方案**:`flutter_local_notifications` + 健康算法 ### 验收标准 -- [ ] "为你推荐"展示个性化推荐菜谱 -- [ ] 每周菜单可规划三餐并生成购物清单 -- [ ] 份量缩放支持多种单位换算 -- [ ] 就寝提醒根据饮食时间智能推荐 +- [x] "为你推荐"展示个性化推荐菜谱 +- [x] 每周菜单可规划三餐并生成购物清单 +- [x] 份量缩放支持多种单位换算 +- [x] 就寝提醒根据饮食时间智能推荐 --- ## 🟢 阶段十四:接口能力挖掘(P1/P2) **目标**:利用已有API接口能力,实现App端未开发的功能 -**前置依赖**:API v2.0.0 已支持完整接口 -**关键阻塞**:无 -**数据来源**:`docs/api/doc/API_DOC.md` + `docs/api/doc/APP_GUIDE.md` - -| 序号 | 任务 | 产出文件 | 优先级 | 状态 | 说明 | -|------|------|---------|--------|------|------| -| 14.1 | 🍽️ 用餐时段推荐 | `lib/src/pages/home/meal_time_recommend.dart` | P1 | ❌ 未实现 | 根据时间推荐早餐/午餐/晚餐 | -| 14.2 | 📊 营养分析增强 | `lib/src/pages/nutrition/nutrition_detail_page.dart` | P1 | ❌ 未实现 | 营养成分详情+趋势图表 | -| 14.3 | ⚠️ 过敏原警示增强 | `lib/src/pages/recipe/recipe_detail_page.dart` | P1 | ❌ 未实现 | 详情页过敏原警示+食材替代建议 | -| 14.4 | 🔥 点赞/推荐系统 | `lib/src/services/action_service.dart` | P2 | ❌ 未实现 | 点赞/取消点赞+五星评分 | -| 14.5 | 📱 社交分享 | `lib/src/pages/recipe/share_recipe_page.dart` | P2 | ❌ 未实现 | 生成分享链接+二维码海报 | -| 14.6 | 👤 个性化信息流 | `lib/src/pages/home_page.dart` | P1 | ❌ 未实现 | 基于用户偏好的首页推荐 | -| 14.7 | 🥕 食材详情页 | `lib/src/pages/ingredient/ingredient_detail_page.dart` | P2 | ❌ 未实现 | 食材介绍+营养+选购指南 | -| 14.8 | 📈 浏览量统计 | `lib/src/services/analytics_service.dart` | P2 | ❌ 未实现 | 增加浏览量+热度标签展示 | - -### 功能详情 - -#### 14.1 用餐时段推荐 -- **接口支持**:`api.php?act=search&keyword=早餐/中餐/晚餐` - ---- ## 🔴 阶段十六:用户体验优化+Bug修复(P0/P1) -**目标**:修复用户反馈的7个严重问题,提升应用稳定性和用户体验 -**发现时间**:2026-04-10(用户反馈) -**关键阻塞**:无 -**优先级**:P0=最高优先级(影响用户使用的严重问题) - -| 序号 | 任务 | 产出文件 | 优先级 | 状态 | 说明 | -|------|------|---------|--------|------|------| -| 16.1 | 🚀 启动加载优化+骨架屏 | `lib/src/pages/home_page.dart` | P0 | ✅ 已完成 | 添加超时保护+骨架屏组件+缓存优先策略 | -| 16.2 | 🛠️ 收藏页面"更多"卡死修复 | `lib/src/pages/favorites/favorites_page.dart` | P0 | ✅ 已完成 | 添加Binding+错误处理+空指针保护 | -| 16.3 | 🔍 搜索详情卡死修复 | `lib/src/pages/search/search_page.dart` | P0 | ✅ 已完成 | 为RecipeDetailPage添加Binding+Controller安全获取 | -| 16.4 | 🎲 今天吃什么动态筛选优化 | `lib/src/pages/what_to_eat/what_to_eat_page.dart` | P1 | ✅ 已完成 | 优化UI显示+添加错误提示+空结果处理 | -| 16.5 | 📊 营养中心报告按钮修复 | `lib/src/pages/nutrition/nutrition_center_page.dart` | P1 | ✅ 已完成 | 检查NutritionBinding+添加错误处理 | -| 16.6 | ❤️ 收藏页面UI重构 | `lib/src/pages/profile/favorites_page.dart` | P2 | ✅ 已完成 | iOS 26 Liquid Glass风格(BackdropFilter+半透明)+优化按钮尺寸 | -| 16.7 | 🔥 热门排行数据修复 | `lib/src/repositories/hot_repository.dart` | P1 | ✅ 已完成 | 检查API返回+添加错误提示+调试日志 | - -### 问题详情 - -#### 16.1 启动加载慢+无骨架屏 -- **现象**:启动应用时 loading 动画超过10秒,首页点击菜谱 loading 超过5秒 -- **原因分析**: - 1. `RecipeRepository.fetchFeedRecipes()` 没有超时保护 - 2. 首页没有骨架屏,只有简单的 loading 动画 - 3. 没有缓存机制,每次都要从网络获取数据 -- **解决方案**: - 1. 添加超时保护(12秒) - 2. 创建骨架屏组件 `SkeletonLoader` - 3. 添加缓存优先策略,优先显示缓存数据 - -#### 16.2 收藏页面点击"更多"卡死闪退 -- **现象**:收藏页面点击"更多"按钮后应用卡死闪退 -- **原因分析**: - 1. `ToolsCenterPage` 使用 `Get.put(ToolsController())` 直接注册 - 2. 可能 `ToolsController` 初始化时出错 - 3. 没有错误处理和空指针保护 -- **解决方案**: - 1. 创建 `ToolsBinding` 并添加到路由配置 - 2. 使用 `Get.find()` 获取 Controller - 3. 添加 try-catch 错误处理 - -#### 16.3 搜索结果点击详情卡死闪退 -- **现象**:搜索后点击详细结果,应用卡死闪退 -- **原因分析**: - 1. `RecipeDetailPage` 使用 `Get.find()` - 2. 如果 Controller 未注册会抛出异常 - 3. `ActionController` 和 `ShoppingListController` 初始化可能失败 -- **解决方案**: - 1. 为 `RecipeDetailPage` 添加完整的 Binding - 2. 所有 Controller 使用 try-catch 保护 - 3. 添加空指针检查和默认值 - -#### 16.4 今天吃什么动态筛选问题 -- **现象**:不支持动态筛选,随机选择有时不显示结果 -- **原因分析**: - 1. 代码已实现动态筛选,但 UI 不够明显 - 2. 随机选择不显示结果可能是 API 返回空数据 - 3. 没有错误提示 -- **解决方案**: - 1. 优化 UI,使筛选功能更明显 - 2. 添加错误提示和空结果处理 - 3. 添加调试日志,排查 API 问题 - -#### 16.5 营养中心报告按钮卡死 -- **现象**:点击右上角"报告"按钮应用卡死闪退,"今天"按钮无反应 -- **原因分析**: - 1. `NutritionReportPage` 使用 `Get.find()` - 2. 路由配置中已有 `NutritionBinding` - 3. 可能是 `MealRecordController` 初始化失败 -- **解决方案**: - 1. 检查 `NutritionBinding` 是否正确注册 - 2. 添加错误处理和空指针保护 - 3. 添加加载状态提示 - -#### 16.6 收藏页面UI设计问题 -- **现象**:排版杂乱无章,按钮太小点不到,不符合操作逻辑 -- **原因分析**: - 1. UI 布局不够清晰 - 2. 按钮尺寸不符合 iOS 设计规范 - 3. 操作流程不顺畅 -- **解决方案**: - 1. 重构 UI,使用 iOS 26 Liquid Glass 风格 - 2. 增大按钮尺寸至最小 44x44 - 3. 优化操作流程和布局比例 - -#### 16.7 热门排行数据为空 -- **现象**:今日浏览量、点赞数、推荐数都显示"暂无数据" -- **原因分析**: - 1. `HotRepository` 调用 `stats_full.php?act=hot` API - 2. API 可能返回空数据或数据结构不匹配 - 3. 没有错误提示 -- **解决方案**: - 1. 检查 API 返回数据结构 - 2. 添加错误提示和空数据处理 - 3. 添加调试日志,排查 API 问题 - -### 开发顺序建议 - -``` -16.1 启动加载优化(影响最大,用户第一印象) - → 16.2 收藏页面"更多"修复(严重闪退) - → 16.3 搜索详情修复(严重闪退) - → 16.5 营养中心修复(功能性问题) - → 16.4 今天吃什么优化(体验问题) - → 16.7 热门排行修复(数据问题) - → 16.6 收藏页面UI重构(体验优化) -``` - -### 验收标准 -- [ ] 启动应用 3 秒内显示骨架屏,5 秒内加载完成 -- [ ] 收藏页面点击"更多"正常跳转,无卡死闪退 -- [ ] 搜索结果点击详情正常跳转,无卡死闪退 -- [ ] 今天吃什么支持动态筛选,随机选择有结果提示 -- [ ] 营养中心报告按钮正常跳转,"今天"按钮有反馈 -- [ ] 收藏页面 UI 整洁美观,按钮易于点击 -- [ ] 热门排行显示真实数据,无数据时有友好提示 ### 技术要点 @@ -399,58 +244,7 @@ - 📊 热度标签展示(🔥爆款/📈热门/❤️受欢迎) - 👥 社交平台分享 -#### 14.6 个性化信息流 -- **接口支持**:`api_feed.php?act=personal&user_id=xxx` -- **功能**: - - 🎯 基于偏好推荐 - - 🚫 自动过滤过敏原 - - 📊 千人千面首页 - - 🔄 智能刷新 -#### 14.7 食材详情页 -- **接口支持**:`api.php?act=ingredient_detail&id=1` -- **返回字段**:`introduction`/`nutrition`/`usage_tip`/`effect`/`guidance` -- **功能**: - - 📖 食材介绍 - - 🥗 营养成分 - - 💡 使用技巧 - - 🏥 食疗功效 - - 🛒 选购指南 - -#### 14.8 浏览量统计 -- **接口支持**: - - `api_action.php?act=view&type=recipe&id=1&count=1` - - `api.php?act=detail&id=xxx&viewnums=true` -- **功能**: - - 📈 增加浏览量 - - 🔥 热门排行统计 - - 📊 用户浏览历史 - - 📈 趋势分析 - -### 开发优先级 - -| 优先级 | 功能 | 说明 | -|--------|------|------| -| **P1** | 用餐时段推荐 | 接口完整,实现简单,用户价值高 | -| **P1** | 个性化信息流 | 接口完整,提升用户体验 | -| **P1** | 过敏原警示增强 | 接口完整,健康安全 | -| **P2** | 点赞/推荐系统 | 接口完整,增加互动 | -| **P2** | 营养分析增强 | 接口完整,健康管理 | -| **P2** | 社交分享 | 接口完整,增加传播 | -| **P2** | 食材详情页 | 接口完整,内容丰富 | -| **P2** | 浏览量统计 | 接口完整,数据驱动 | - -### 验收标准 -- [ ] 首页根据时段推荐早餐/午餐/晚餐 -- [ ] 营养中心展示详细营养成分 -- [ ] 详情页显示过敏原警示 -- [ ] 详情页可点赞/评分 -- [ ] 详情页可分享菜谱 -- [ ] 首页个性化推荐 -- [ ] 食材可查看详情页 -- [ ] 浏览量正确统计 - ---- ## 🔴 阶段十五:后端接口增强(P1/P2) @@ -468,56 +262,6 @@ | 15.5 | 📜 浏览历史同步 | `api_history.php?act=add/list` | P2 | ❌ 未实现 | 浏览历史云端存储 | | 15.6 | 📝 菜谱上传 | `api_recipe.php?act=add/edit/delete` | P2 | ❌ 未实现 | 用户菜谱上传/编辑 | -### 功能详情 - -#### 15.1 用户注册登录 -- **现状**:❌ 无注册登录接口 -- **建议接口**: - ``` - POST api_user.php?act=register - { "username": "xxx", "password": "xxx", "email": "xxx" } - - POST api_user.php?act=login - { "username": "xxx", "password": "xxx" } - - GET api_user.php?act=profile&user_id=xxx - ``` -- **功能**: - - 📱 手机号注册/登录 - - 🔗 第三方登录(微信/Apple ID) - - 👤 用户资料管理 - - 🔐 密码找回 - -#### 15.2 收藏云端同步 -- **现状**:⚠️ 仅本地 Hive 存储 -- **建议接口**: - ``` - POST api_favorite.php?act=add - { "user_id": "xxx", "recipe_id": 123 } - - GET api_favorite.php?act=list&user_id=xxx - - DELETE api_favorite.php?act=remove - { "user_id": "xxx", "recipe_id": 123 } - ``` -- **功能**: - - ☁️ 收藏云端存储 - - 🔄 多设备同步 - - 📂 收藏分类管理 - - 🔗 收藏分享 - -#### 15.3 评论系统 -- **现状**:❌ 无评论接口 -- **建议接口**: - ``` - GET api_comment.php?act=list&recipe_id=123&page=1 - - POST api_comment.php?act=add - { "user_id": "xxx", "recipe_id": 123, "content": "xxx" } - - DELETE api_comment.php?act=delete - { "user_id": "xxx", "comment_id": 456 } - ``` - **功能**: - 💬 发表评论 - 👍 评论点赞 @@ -527,12 +271,7 @@ #### 15.4 消息推送 - **现状**:❌ 无推送接口 - **建议接口**: - ``` - GET api_message.php?act=list&user_id=xxx - - POST api_message.php?act=read - { "user_id": "xxx", "message_id": 123 } - ``` + - **功能**: - 📬 站内信 - 🔔 推送通知 @@ -542,10 +281,10 @@ #### 15.5 浏览历史同步 - **现状**:⚠️ 仅本地存储 - **建议接口**: - ``` + ```text POST api_history.php?act=add { "user_id": "xxx", "recipe_id": 123 } - + GET api_history.php?act=list&user_id=xxx&page=1 ``` - **功能**: @@ -557,16 +296,7 @@ #### 15.6 菜谱上传 - **现状**:❌ 无上传接口 - **建议接口**: - ``` - POST api_recipe.php?act=add - { "title": "xxx", "ingredients": [...], "steps": [...] } - - PUT api_recipe.php?act=edit - { "recipe_id": 123, "title": "xxx" } - - DELETE api_recipe.php?act=delete - { "recipe_id": 123 } - ``` + ```text - **功能**: - 📝 用户菜谱上传 - ✏️ 菜谱编辑 @@ -602,26 +332,28 @@ | 功能 | 状态 | 首次版本 | 说明 | |------|------|---------|------| | 热量追踪+营养分析 | ⚠️ 部分完成 | v0.3x | 环形图+柱状图+目标设置 ✅;饼图+折线图 ❌ | -| 购物清单 | ⚠️ 部分完成 | v0.4x | 添加/删除/勾选/分类 ✅;从菜谱添加 ❌(页面无入口) | +| 购物清单 | ✅ 已完成 | v0.4x | 添加/删除/勾选/分类 ✅;从菜谱添加 ✅(菜谱详情页"购物"按钮) | | 烹饪计时器 | ✅ 已完成 | v0.5x | 多步骤倒计时 | | 用量换算 | ✅ 已完成 | v0.5x | 常用单位换算 | -| 过敏原检测 | ❌ 未完成 | v0.5x | 标记含过敏原菜谱 — 空壳实现,checkAllergens永远返回空列表 | -| 烹饪笔记 | ❌ 未完成 | v0.5x | 按菜谱关联笔记 — 仅有Controller+Model,无页面 | +| 过敏原检测 | ✅ 已完成 | v0.5x | 标记含过敏原菜谱 ✅;AllergenChecker完整实现 | +| 烹饪笔记 | ✅ 已完成 | v0.5x | 按菜谱关联笔记 ✅;CookingNotePage完整实现 | | BMI 计算器 | ✅ 已完成 | v0.5x | 含健康建议 | -| 份量缩放 | ⚠️ 部分完成 | v0.5x | 按比例调整 ✅;食材列表硬编码5项,不支持从菜谱导入 ❌ | +| 份量缩放 | ✅ 已完成 | v0.5x | 按比例调整 ✅;支持从菜谱导入 ✅(菜谱详情页"缩放"按钮) | | 主页体验优化 | ✅ 已完成 | v0.6x | 骨架屏+动画+搜索+详情页 | | 今天吃什么增强 | ✅ 已完成 | v0.7x | 分类/标签/过敏原三维筛选 | | API v2.0.0 迁移 | ✅ 已完成 | v0.8x | 合并接口+8个Bug修复 | | 动态主题 | ✅ 已完成 | v0.6x | 多主题色+暗色模式+卡片滑动方向 | | Liquid Glass 风格 | ✅ 已完成 | v0.6x | 底栏+搜索栏+分段控件+卡片 | | 收藏管理 | ✅ 已完成 | v0.8x | 编辑/排序/分类/跳转详情 | -| 静态分析清理 | ✅ 已完成 | v0.52 | 107→1 个 info,0 error/warning | +| 静态分析清理 | ✅ 已完成 | v0.52 | 1071 个 info,0 error/warning | | 工具中心 | ✅ 已完成 | v0.9x | 工具入口Bar+分类筛选+使用频率统计 | | 过敏原检查工具 | ✅ 已完成 | v0.9x | 食材过敏原查询与分类浏览(API数据源) | | 用餐时段推荐 | ✅ 已完成 | v0.9x | 根据时间推荐早中晚餐菜谱 | -| 每周菜单规划 | ⚠️ 部分完成 | v0.9x | 一周三餐UI ✅;数据不持久化 ❌;从收藏添加 ❌(仅snackbar提示) | -| 食材详情查询 | ⚠️ 部分完成 | v0.9x | 食材列表+搜索 ✅;营养信息与选购指南 ❌(仅显示名称和分类) | +| 每周菜单规划 | ✅ 已完成 | v0.88x | 一周三餐UI ✅;数据持久化 ✅(Hive);从菜谱添加 ✅ | +| 食材详情查询 | ✅ 已完成 | v0.9x | 食材列表+搜索 ✅;营养信息+选购指南+存储提示 ✅ | | 统一Controller Binding | ✅ 已完成 | v0.63 | AppBinding全局管理+移除重复注册 | +| AI菜谱推荐 | ✅ 已完成 | v0.88x | 基于用户偏好+浏览历史+收藏记录的智能推荐 ✅ | +| 就寝提醒 | ✅ 已完成 | v0.88x | 智能推荐就寝时间+睡前进食提醒 ✅ | --- @@ -931,3 +663,190 @@ lib/src/ **19.18 网络请求优化** - 问题:ApiService日志拦截器无输出、无重试机制、post/put/delete重复代码 - 方案:增强日志拦截器(debugPrint请求/响应/错误)、添加_executeWithRetry(最多2次重试)、统一_executeWithOfflineCheck、修复_tryGetCache缓存数据jsonDecode + +### 验收标准 +- [x] 发现页更多按钮正常跳转工具中心 +- [x] 烹饪计时器支持常用预设快速添加 +- [x] 菜谱详情显示全部API数据 +- [x] 口味偏好分类和标签正确解析 +- [x] 热门排行有fallback机制 +- [x] 购物清单按钮易于点击 +- [x] 我的页面支持左右滑动切换 +- [x] 笔记保存后立即显示 +- [x] 深色模式支持跟随系统 +- [x] 字体大小全局生效 +- [x] 底部Tab栏适配安全区域 +- [x] 白色区域不再遮住底部 +- [x] 推荐分类支持层级导航 +- [x] 今天吃什么正常加载 +- [x] 搜索无结果时显示相似推荐 +- [x] 用餐时段推荐页可浏览 +- [x] 网络请求有日志和重试 + +--- + +## 🟢 阶段二十:用户体验优化+交互增强(P0/P1/P2)— ✅ 已完成 + +**目标**:修复关键Bug,优化用户体验,增强交互功能 +**完成时间**:2026-04-11 +**关键阻塞**:无 +**优先级**:P0=崩溃/闪退 P1=功能缺陷 P2=体验优化 + +### 问题清单 + +| 序号 | 任务 | 产出文件 | 优先级 | 状态 | 说明 | +|------|------|---------|--------|------|------| +| 20.1 | 🐛 笔记按钮闪退修复 | `cooking_note_page.dart` | P0 | ✅ 已完成 | 安全Controller初始化,防止空指针异常 | +| 20.2 | 🚀 详情页骨架屏加载 | `recipe_detail_page.dart` | P1 | ✅ 已完成 | 骨架屏+8秒超时保护+重试按钮 | +| 20.3 | 🔍 发现页推荐导航优化 | `discover_page.dart` + `category_browse_page.dart` | P1 | ✅ 已完成 | 智能路由:子分类/直接菜谱列表/分类浏览 | +| 20.4 | 🖼️ 图片Invalid image data错误 | `recipe_image.dart` | P0 | ✅ 已完成 | 图片格式验证(魔数检测)+自动Fallback | +| 20.5 | ⚠️ 工具中心GetX警告+溢出 | `tools_center_page.dart` | P1 | ✅ 已完成 | StatefulWidget重构+动态padding计算 | +| 20.6 | 👆 发现页左右滑动功能 | `discover_page.dart` | P2 | ✅ 已完成 | Dismissible组件+快速操作菜单 | +| 20.7 | 🍽️ 用餐时段图片加载 | `meal_time_recommend_page.dart` | P1 | ✅ 已完成 | 引入RecipeImage组件显示封面图 | + +### 实施记录 + +**20.1 笔记按钮闪退修复** +- **问题**:菜谱详情页点击"笔记"按钮导致应用卡死/闪退 +- **根因**:Controller未安全初始化,空指针异常 +- **方案**: + - 实现安全的Controller获取模式(带错误状态) + - 添加_controllerError标志位防止重复异常 + - 初始化时try-catch包裹,失败时显示友好提示 + - 所有Controller调用使用getter安全访问 + +**20.2 详情页骨架屏加载** +- **问题**:菜谱详情页加载超过5秒,用户体验差 +- **方案**: + - 实现完整骨架屏(封面图250px + 标题描述 + 统计数据4列 + 内容区域) + - 8秒超时检测,超时后显示提示和重试按钮 + - ActionController/FavoritesController安全获取,所有操作添加try-catch + +**20.3 发现页推荐导航优化** +- **问题1**:点击推荐下的"菜谱"分类进入后不显示子类 +- **问题2**:点击"食材"分类显示"暂无菜谱" +- **方案**: + - 分类点击智能路由:有子分类→子类浏览 / 有菜谱数→直接加载 / 都没有→分类浏览 + - 新增loadRecipesDirectly参数支持直接加载模式 + - CategoryBrowsePage支持三种显示模式 + +**20.4 图片Invalid image data错误** +- **问题**:多个页面显示"Exception: Invalid image data" +- **根因**:API返回的非图片数据被当作图片解析 +- **方案**: + - 新增_isValidImageData()方法验证图片格式 + - 支持JPEG/PNG/GIF/WebP/BMP格式检测(文件头魔数判断) + - 无效数据自动触发Fallback链,尝试下一个URL + - errorBuilder回调中增加自动重试机制 + +**20.5 工具中心GetX警告+布局溢出** +- **问题1**:工具中心显示"use of a GetX has been detected"警告 +- **问题2**:底部溢出54像素 +- **方案**: + - 重构为StatefulWidget,安全初始化Controller + - GridView使用childAspectRatio: 0.85替代固定高度 + - padding动态计算底部安全区域 + +**20.6 发现页左右滑动功能** +- **新功能**:热门列表支持左右滑动操作 +- **交互设计**: + - 右滑(绿色背景):查看详情 + - 左滑(蓝色背景):快速操作菜单(收藏/查看详情/分享) +- **技术实现**:Dismissible组件 + CupertinoActionSheet + +**20.7 用餐时段图片加载优化** +- **问题**:用餐时段页面显示图片错误 +- **方案**:引入RecipeImage组件显示菜谱封面图,自动享受Fallback链+错误处理 + +### 验收标准 +- [x] 笔记按钮点击正常,不闪退 +- [x] 详情页3秒内显示骨架屏,8秒内加载完成或显示重试 +- [x] 发现页推荐分类导航正确 +- [x] 图片加载失败时自动Fallback,不显示错误信息 +- [x] 工具中心无GetX警告,无布局溢出 +- [x] 发现页热门列表支持左右滑动 +- [x] 用餐时段页面正常显示菜谱封面图 + +--- + +## 🟢 阶段二十一:菜谱详情页功能增强(P2)— ✅ 已完成 + +**目标**:增强菜谱详情页功能,方便开发者调试和用户查看图片信息 +**完成时间**:2026-04-11 +**关键阻塞**:无 +**优先级**:P2=功能增强 + +### 问题清单 + +| 序号 | 任务 | 产出文件 | 优先级 | 状态 | 说明 | +|------|------|---------|--------|------|------| +| 21.1 | 🖼️ Picid显示和复制功能 | `recipe_detail_page.dart` | P2 | ✅ 已完成 | 在作者卡片下方显示图片信息卡片 | + +### 实施记录 + +**21.1 Picid显示和复制功能** +- **需求**:在菜谱详情页用户名称下方显示图片信息和Picid +- **功能实现**: + - 新增图片信息卡片,显示在作者卡片下方 + - 显示Picid编号(可选中复制) + - 显示图片链接(可选中复制) + - 点击卡片一键复制Picid到剪贴板 + - 复制成功后显示Toast提示 +- **UI设计**: + - 卡片式设计,带主题色边框 + - 右上角显示"点击复制"提示标签 + - Picid使用等宽字体(monospace)高亮显示 + - 图片链接支持多行显示 +- **技术细节**: + - 导入 `flutter/services.dart` 使用 Clipboard + - 使用 SelectableText 支持文本选中 + - 无Picid时显示"无",但仍显示图片链接 + +### 验收标准 +- [x] 菜谱详情页显示图片信息卡片 +- [x] Picid可选中复制 +- [x] 图片链接可选中复制 +- [x] 点击卡片一键复制Picid +- [x] 复制成功显示Toast提示 + +--- + +## 🟢 阶段二十二:Picid功能Bug修复(P0)— ✅ 已完成 + +**目标**:修复Picid显示为0和点击复制卡死闪退的问题 +**完成时间**:2026-04-11 +**关键阻塞**:无 +**优先级**:P0=崩溃/闪退 + +### 问题清单 + +| 序号 | 任务 | 产出文件 | 优先级 | 状态 | 说明 | +|------|------|---------|--------|------|------| +| 22.1 | 🐛 Picid显示为0 | `recipe_detail_page.dart` | P0 | ✅ 已完成 | 添加有效性检查,无效时显示"无" | +| 22.2 | 🐛 点击复制卡死闪退 | `recipe_detail_page.dart` | P0 | ✅ 已完成 | 安全Toast显示+异常捕获 | + +### 实施记录 + +**22.1 Picid显示为0的问题** +- **问题**:所有菜谱的Picid都显示为0 +- **根因**:API返回的pic_id字段可能不存在或为null,_parseInt返回默认值0 +- **方案**: + - 添加有效性检查:`hasValidPicId = picId != null && picId > 0` + - 无效Picid时显示"无",不显示0 + - 无有效Picid时图片链接显示"暂无图片链接" + - 添加调试日志输出picId实际值 + +**22.2 点击复制卡死闪退** +- **问题**:点击Picid卡片复制时应用卡死闪退 +- **根因**:ToastService.show可能因ThemeService未注册而崩溃 +- **方案**: + - 新增 `_showCopyToast()` 方法安全显示Toast + - 添加mounted检查防止Widget已销毁 + - Clipboard.setData包裹try-catch防止异常 + - 失败时显示友好提示而非崩溃 + +### 验收标准 +- [x] Picid为0或null时显示"无" +- [x] 点击复制不卡死不闪退 +- [x] 复制成功显示Toast提示 +- [x] 复制失败显示友好提示 diff --git a/lib/main.dart b/lib/main.dart index b1ec422..2845098 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -83,11 +83,15 @@ class MyApp extends StatelessWidget { getPages: AppRoutes.pages, initialBinding: AppBinding(), builder: (context, widget) { + final theme = ThemeService.instance; return MediaQuery( data: MediaQuery.of( context, ).copyWith(textScaler: TextScaler.linear(textScale)), - child: widget!, + child: ColoredBox( + color: theme.backgroundColor.value, + child: widget!, + ), ); }, routingCallback: (routing) { diff --git a/lib/src/app_binding.dart b/lib/src/app_binding.dart index f1ad688..cba4445 100644 --- a/lib/src/app_binding.dart +++ b/lib/src/app_binding.dart @@ -19,6 +19,8 @@ import 'package:mom_kitchen/src/controllers/shopping_list_controller.dart'; import 'package:mom_kitchen/src/controllers/cooking_note_controller.dart'; import 'package:mom_kitchen/src/controllers/tools_controller.dart'; import 'package:mom_kitchen/src/controllers/what_to_eat_controller.dart'; +import 'package:mom_kitchen/src/controllers/weekly_menu_controller.dart'; +import 'package:mom_kitchen/src/controllers/bedtime_reminder_controller.dart'; import 'package:mom_kitchen/src/services/core/app_service.dart'; import 'package:mom_kitchen/src/services/ui/theme_service.dart'; @@ -56,6 +58,8 @@ class AppBinding extends Bindings { Get.put(HotController(), permanent: true); Get.put(WhatToEatController(), permanent: true); Get.put(CookingNoteController(), permanent: true); + Get.put(WeeklyMenuController(), permanent: true); + Get.put(BedtimeReminderController(), permanent: true); } } diff --git a/lib/src/config/app_routes.dart b/lib/src/config/app_routes.dart index 3957af5..0cff171 100644 --- a/lib/src/config/app_routes.dart +++ b/lib/src/config/app_routes.dart @@ -29,6 +29,8 @@ import 'package:mom_kitchen/src/pages/tools/ingredient_detail_page.dart'; import 'package:mom_kitchen/src/pages/tools/cooking_note_page.dart'; import 'package:mom_kitchen/src/pages/discover/category_browse_page.dart'; import 'package:mom_kitchen/src/pages/tools/eating_times_page.dart'; +import 'package:mom_kitchen/src/pages/tools/weekly_menu_planner_page.dart'; +import 'package:mom_kitchen/src/pages/profile/bedtime_reminder_page.dart'; import 'package:mom_kitchen/src/app_binding.dart'; class AppRoutes { @@ -65,6 +67,8 @@ class AppRoutes { static const String cookingNote = '/cooking-note'; static const String categoryBrowse = '/category-browse'; static const String eatingTimes = '/eating-times'; + static const String weeklyMenuPlanner = '/tools/weekly-menu'; + static const String bedtimeReminder = '/profile/bedtime-reminder'; static final List pages = [ GetPage( @@ -246,6 +250,8 @@ class AppRoutes { page: () => CategoryBrowsePage( category: Get.arguments?['category'], title: Get.arguments?['title'] ?? '分类浏览', + loadRecipesDirectly: + Get.arguments?['loadRecipesDirectly'] ?? false, ), middlewares: [PageStandardsMiddleware()], ), @@ -254,6 +260,16 @@ class AppRoutes { page: () => const EatingTimesPage(), middlewares: [PageStandardsMiddleware()], ), + GetPage( + name: weeklyMenuPlanner, + page: () => const WeeklyMenuPlannerPage(), + middlewares: [PageStandardsMiddleware()], + ), + GetPage( + name: bedtimeReminder, + page: () => const BedtimeReminderPage(), + middlewares: [PageStandardsMiddleware()], + ), ]; static void registerAllPages() { @@ -507,6 +523,18 @@ class AppRoutes { ], builder: () => const ServingScalerPage(), ), + PageInfo( + route: toolsCenter, + name: 'Tools Center Page', + description: '工具中心页面', + requiredStandards: const [ + StandardCheck.themeColors, + StandardCheck.textColors, + StandardCheck.fontSize, + StandardCheck.darkMode, + ], + builder: () => const ToolsCenterPage(), + ), ]); } } diff --git a/lib/src/config/design_tokens.dart b/lib/src/config/design_tokens.dart index 4cefdd5..8b6b10f 100644 --- a/lib/src/config/design_tokens.dart +++ b/lib/src/config/design_tokens.dart @@ -10,6 +10,29 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/services/ui/theme_service.dart'; +/// 创建统一的 TextStyle,自动设置 inherit: false +/// 避免 "Failed to interpolate TextStyles with different inherit" 错误 +TextStyle createTextStyle({ + required Color color, + double fontSize = 14.0, + FontWeight fontWeight = FontWeight.normal, + double? height, + TextDecoration? decoration, + Color? decorationColor, + double? letterSpacing, +}) { + return TextStyle( + inherit: false, + color: color, + fontSize: fontSize, + fontWeight: fontWeight, + height: height, + decoration: decoration, + decorationColor: decorationColor, + letterSpacing: letterSpacing, + ); +} + class DesignTokens { DesignTokens._(); diff --git a/lib/src/controllers/bedtime_reminder_controller.dart b/lib/src/controllers/bedtime_reminder_controller.dart new file mode 100644 index 0000000..9c92316 --- /dev/null +++ b/lib/src/controllers/bedtime_reminder_controller.dart @@ -0,0 +1,195 @@ +/* + * 文件: bedtime_reminder_controller.dart + * 名称: 就寝提醒控制器 + * 作用: 根据晚餐时间推荐就寝时间,睡前不宜进食提醒 + * 创建: 2026-04-11 + * 更新: 2026-04-11 初始实现 + */ + +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/controllers/base_controller.dart'; +import 'package:mom_kitchen/src/services/data/hive_service.dart'; +import 'package:mom_kitchen/src/services/ui/toast_service.dart'; + +class BedtimeReminderController extends BaseController { + // 晚餐时间(小时,24小时制) + final RxInt dinnerHour = 19.obs; + final RxInt dinnerMinute = 0.obs; + + // 推荐就寝时间 + final RxInt recommendedBedtimeHour = 22.obs; + final RxInt recommendedBedtimeMinute = 0.obs; + + // 提醒设置 + final RxBool reminderEnabled = false.obs; + final RxBool advanceReminderEnabled = true.obs; + final RxInt advanceReminderMinutes = 30.obs; + + // 睡前进食提醒 + final RxBool beforeSleepEatingReminder = true.obs; + final RxInt beforeSleepEatingThreshold = 2.obs; // 睡前2小时内不宜进食 + + static const String _hiveBox = 'bedtime_reminder'; + + @override + void onInit() { + super.onInit(); + _loadSettings(); + _calculateRecommendedBedtime(); + } + + void _loadSettings() { + try { + final hive = HiveService(); + if (!hive.isInitialized) { + _setDefaultValues(); + return; + } + + final data = hive.get(_hiveBox, 'settings'); + if (data != null) { + dinnerHour.value = data['dinner_hour'] ?? 19; + dinnerMinute.value = data['dinner_minute'] ?? 0; + reminderEnabled.value = data['reminder_enabled'] ?? false; + advanceReminderEnabled.value = data['advance_reminder_enabled'] ?? true; + advanceReminderMinutes.value = data['advance_reminder_minutes'] ?? 30; + beforeSleepEatingReminder.value = data['before_sleep_eating_reminder'] ?? true; + beforeSleepEatingThreshold.value = data['before_sleep_eating_threshold'] ?? 2; + } else { + _setDefaultValues(); + } + } catch (e) { + debugPrint('Load bedtime reminder settings error: $e'); + _setDefaultValues(); + } + } + + void _setDefaultValues() { + dinnerHour.value = 19; + dinnerMinute.value = 0; + reminderEnabled.value = false; + advanceReminderEnabled.value = true; + advanceReminderMinutes.value = 30; + beforeSleepEatingReminder.value = true; + beforeSleepEatingThreshold.value = 2; + } + + void _saveSettings() { + try { + final hive = HiveService(); + if (!hive.isInitialized) return; + + hive.put(_hiveBox, 'settings', { + 'dinner_hour': dinnerHour.value, + 'dinner_minute': dinnerMinute.value, + 'reminder_enabled': reminderEnabled.value, + 'advance_reminder_enabled': advanceReminderEnabled.value, + 'advance_reminder_minutes': advanceReminderMinutes.value, + 'before_sleep_eating_reminder': beforeSleepEatingReminder.value, + 'before_sleep_eating_threshold': beforeSleepEatingThreshold.value, + }); + } catch (e) { + debugPrint('Save bedtime reminder settings error: $e'); + } + } + + void updateDinnerTime(int hour, int minute) { + dinnerHour.value = hour; + dinnerMinute.value = minute; + _calculateRecommendedBedtime(); + _saveSettings(); + } + + void _calculateRecommendedBedtime() { + // 根据晚餐时间推荐就寝时间:晚餐后3-4小时 + final dinnerTotalMinutes = dinnerHour.value * 60 + dinnerMinute.value; + final bedtimeTotalMinutes = dinnerTotalMinutes + (3 * 60); // 默认3小时后 + + var bedtimeHour = bedtimeTotalMinutes ~/ 60; + var bedtimeMinute = bedtimeTotalMinutes % 60; + + if (bedtimeHour >= 24) { + bedtimeHour -= 24; + } + + recommendedBedtimeHour.value = bedtimeHour; + recommendedBedtimeMinute.value = bedtimeMinute; + } + + void toggleReminder(bool enabled) { + reminderEnabled.value = enabled; + _saveSettings(); + + if (enabled) { + ToastService.show(message: '已启用就寝提醒 🔔'); + // TODO: 实际设置本地通知 + } else { + ToastService.show(message: '已关闭就寝提醒 🔕'); + // TODO: 取消本地通知 + } + } + + void toggleAdvanceReminder(bool enabled) { + advanceReminderEnabled.value = enabled; + _saveSettings(); + } + + void updateAdvanceReminderMinutes(int minutes) { + advanceReminderMinutes.value = minutes; + _saveSettings(); + } + + void toggleBeforeSleepEatingReminder(bool enabled) { + beforeSleepEatingReminder.value = enabled; + _saveSettings(); + } + + void updateBeforeSleepEatingThreshold(int hours) { + beforeSleepEatingThreshold.value = hours; + _saveSettings(); + } + + String get dinnerTimeDisplay => + '${dinnerHour.value.toString().padLeft(2, '0')}:${dinnerMinute.value.toString().padLeft(2, '0')}'; + + String get recommendedBedtimeDisplay => + '${recommendedBedtimeHour.value.toString().padLeft(2, '0')}:${recommendedBedtimeMinute.value.toString().padLeft(2, '0')}'; + + String get bedtimeRecommendation { + final hoursAfterDinner = 3; + return '建议晚餐后 $hoursAfterDinner 小时就寝,有利于消化和睡眠质量'; + } + + String get beforeSleepEatingWarning { + final threshold = beforeSleepEatingThreshold.value; + return '睡前 $threshold 小时内不宜进食,以免影响消化和睡眠'; + } + + bool shouldShowBeforeSleepEatingWarning() { + if (!beforeSleepEatingReminder.value) return false; + + // 添加时间有效性检查 + if (dinnerHour.value < 0 || dinnerHour.value > 23 || + dinnerMinute.value < 0 || dinnerMinute.value > 59) { + debugPrint('Invalid dinner time: ${dinnerHour.value}:${dinnerMinute.value}'); + return false; + } + + final now = DateTime.now(); + final dinnerTime = DateTime( + now.year, + now.month, + now.day, + dinnerHour.value, + dinnerMinute.value, + ); + + final thresholdMinutes = beforeSleepEatingThreshold.value * 60; + final minutesSinceDinner = now.difference(dinnerTime).inMinutes; + + // 如果距离晚餐时间超过阈值,并且还未到推荐就寝时间 + return minutesSinceDinner > thresholdMinutes && + minutesSinceDinner < (3 * 60); + } +} diff --git a/lib/src/controllers/tools_controller.dart b/lib/src/controllers/tools_controller.dart index 4b5f535..4073b79 100644 --- a/lib/src/controllers/tools_controller.dart +++ b/lib/src/controllers/tools_controller.dart @@ -34,18 +34,26 @@ class ToolsController extends BaseController { @override void onInit() { super.onInit(); - _loadTools(); + // 先使用默认工具列表初始化,避免阻塞 + tools.value = ToolRegistry.defaultTools; + filteredTools.value = tools; + // 异步加载使用统计数据 + _loadUsageData(); } - Future _loadTools() async { - isLoading.value = true; - + Future _loadUsageData() async { try { final prefs = await SharedPreferences.getInstance(); final usageData = prefs.getString(_usageKey); - final usageMap = usageData != null - ? Map.from(json.decode(usageData)) - : {}; + Map usageMap = {}; + + if (usageData != null && usageData.isNotEmpty) { + try { + usageMap = Map.from(json.decode(usageData)); + } catch (e) { + debugPrint('ToolsController: Failed to parse usage data: $e'); + } + } tools.value = ToolRegistry.defaultTools.map((tool) { return tool.copyWith(usageCount: usageMap[tool.id] ?? 0); @@ -53,11 +61,46 @@ class ToolsController extends BaseController { _applyFilter(); } catch (e) { - debugPrint('Load tools error: $e'); + debugPrint('ToolsController: Load usage data error: $e'); + } + } + + Future _loadTools() async { + isLoading.value = true; + + try { + debugPrint('ToolsController: Loading tools...'); + final prefs = await SharedPreferences.getInstance(); + debugPrint('ToolsController: SharedPreferences loaded'); + + final usageData = prefs.getString(_usageKey); + Map usageMap = {}; + + if (usageData != null && usageData.isNotEmpty) { + try { + usageMap = Map.from(json.decode(usageData)); + debugPrint('ToolsController: Usage data loaded: $usageMap'); + } catch (e) { + debugPrint('ToolsController: Failed to parse usage data: $e'); + // 解析失败时使用空映射 + usageMap = {}; + } + } + + tools.value = ToolRegistry.defaultTools.map((tool) { + return tool.copyWith(usageCount: usageMap[tool.id] ?? 0); + }).toList(); + + debugPrint('ToolsController: Loaded ${tools.length} tools'); + _applyFilter(); + } catch (e) { + debugPrint('ToolsController: Load tools error: $e'); + // 发生错误时使用默认工具列表 tools.value = ToolRegistry.defaultTools; filteredTools.value = tools; } finally { isLoading.value = false; + debugPrint('ToolsController: Loading completed'); } } diff --git a/lib/src/controllers/weekly_menu_controller.dart b/lib/src/controllers/weekly_menu_controller.dart new file mode 100644 index 0000000..0bed638 --- /dev/null +++ b/lib/src/controllers/weekly_menu_controller.dart @@ -0,0 +1,328 @@ +/* + * 文件: weekly_menu_controller.dart + * 名称: 每周菜单控制器 + * 作用: 管理每周菜单规划数据 + * 创建: 2026-04-11 + * 更新: 2026-04-11 初始实现 + */ + +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/controllers/base_controller.dart'; +import 'package:mom_kitchen/src/models/recipe/recipe_model.dart'; +import 'package:mom_kitchen/src/models/weekly_menu_model.dart'; +import 'package:mom_kitchen/src/services/data/hive_service.dart'; +import 'package:mom_kitchen/src/services/ui/toast_service.dart'; + +class WeeklyMenuController extends BaseController { + final Rx currentMenu = Rx(null); + final RxString selectedDateKey = ''.obs; + final RxString selectedMealType = ''.obs; // breakfast, lunch, dinner + + static const String _hiveBox = 'weekly_menu'; + + @override + void onInit() { + super.onInit(); + _loadCurrentWeekMenu(); + _selectToday(); + } + + void _selectToday() { + final today = DateTime.now(); + selectedDateKey.value = _formatDateKey(today); + selectedMealType.value = 'dinner'; + } + + String _formatDateKey(DateTime date) { + return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; + } + + void _loadCurrentWeekMenu() { + try { + final hive = HiveService(); + if (!hive.isInitialized) { + _createNewWeekMenu(); + return; + } + + final weekId = _getCurrentWeekId(); + final data = hive.get(_hiveBox, weekId); + + if (data != null) { + currentMenu.value = WeeklyMenuModel.fromJson(data); + } else { + _createNewWeekMenu(); + } + } catch (e) { + debugPrint('Load weekly menu error: $e'); + _createNewWeekMenu(); + } + } + + String _getCurrentWeekId() { + final now = DateTime.now(); + final monday = now.subtract(Duration(days: now.weekday - 1)); + return 'week_${monday.year}_${monday.month}_${monday.day}'; + } + + void _createNewWeekMenu() { + final now = DateTime.now(); + final monday = now.subtract(Duration(days: now.weekday - 1)); + final weekId = _getCurrentWeekId(); + + final dailyMenus = {}; + for (int i = 0; i < 7; i++) { + final date = monday.add(Duration(days: i)); + final dateKey = _formatDateKey(date); + dailyMenus[dateKey] = DayMenu(dateKey: dateKey); + } + + currentMenu.value = WeeklyMenuModel( + weekId: weekId, + startDate: monday, + dailyMenus: dailyMenus, + ); + + _saveToHive(); + } + + void _saveToHive() { + try { + final hive = HiveService(); + if (!hive.isInitialized) return; + + if (currentMenu.value != null) { + hive.put(_hiveBox, currentMenu.value!.weekId, currentMenu.value!.toJson()); + } + } catch (e) { + debugPrint('Save weekly menu error: $e'); + } + } + + void selectDate(String dateKey) { + selectedDateKey.value = dateKey; + } + + void selectMealType(String mealType) { + selectedMealType.value = mealType; + } + + void addMealToDay(String dateKey, String mealType, RecipeModel recipe) { + if (currentMenu.value == null) return; + + final dayMenu = currentMenu.value!.dailyMenus[dateKey]; + if (dayMenu == null) return; + + final ingredients = recipe.ingredients.map((ing) { + return ShoppingIngredient( + name: ing.name, + amount: ing.amount ?? '0', + unit: ing.unit ?? '', + checked: false, + ); + }).toList(); + + final mealItem = MealItem( + recipeId: recipe.id, + recipeTitle: recipe.title, + cover: recipe.cover, + ingredients: ingredients, + ); + + final updatedMenus = Map.from(currentMenu.value!.dailyMenus); + + switch (mealType) { + case 'breakfast': + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: mealItem, + lunch: dayMenu.lunch, + dinner: dayMenu.dinner, + ); + break; + case 'lunch': + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: dayMenu.breakfast, + lunch: mealItem, + dinner: dayMenu.dinner, + ); + break; + case 'dinner': + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: dayMenu.breakfast, + lunch: dayMenu.lunch, + dinner: mealItem, + ); + break; + } + + currentMenu.value = WeeklyMenuModel( + weekId: currentMenu.value!.weekId, + startDate: currentMenu.value!.startDate, + dailyMenus: updatedMenus, + ); + + _saveToHive(); + ToastService.show(message: '已添加到${_getMealTypeName(mealType)} ✅'); + } + + void removeMealFromDay(String dateKey, String mealType) { + if (currentMenu.value == null) return; + + final dayMenu = currentMenu.value!.dailyMenus[dateKey]; + if (dayMenu == null) return; + + final updatedMenus = Map.from(currentMenu.value!.dailyMenus); + + switch (mealType) { + case 'breakfast': + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: null, + lunch: dayMenu.lunch, + dinner: dayMenu.dinner, + ); + break; + case 'lunch': + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: dayMenu.breakfast, + lunch: null, + dinner: dayMenu.dinner, + ); + break; + case 'dinner': + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: dayMenu.breakfast, + lunch: dayMenu.lunch, + dinner: null, + ); + break; + } + + currentMenu.value = WeeklyMenuModel( + weekId: currentMenu.value!.weekId, + startDate: currentMenu.value!.startDate, + dailyMenus: updatedMenus, + ); + + _saveToHive(); + ToastService.show(message: '已移除${_getMealTypeName(mealType)} 🗑️'); + } + + void toggleIngredientChecked(String dateKey, String ingredientName) { + if (currentMenu.value == null) return; + + final dayMenu = currentMenu.value!.dailyMenus[dateKey]; + if (dayMenu == null) return; + + final updatedMenus = Map.from(currentMenu.value!.dailyMenus); + + // 更新所有餐次的食材状态 + MealItem? updateMealIngredients(MealItem? meal) { + if (meal == null) return null; + + final updatedIngredients = meal.ingredients.map((ing) { + if (ing.name == ingredientName) { + return ShoppingIngredient( + name: ing.name, + amount: ing.amount, + unit: ing.unit, + checked: !ing.checked, + ); + } + return ing; + }).toList(); + + return MealItem( + recipeId: meal.recipeId, + recipeTitle: meal.recipeTitle, + cover: meal.cover, + ingredients: updatedIngredients, + ); + } + + updatedMenus[dateKey] = DayMenu( + dateKey: dateKey, + breakfast: updateMealIngredients(dayMenu.breakfast), + lunch: updateMealIngredients(dayMenu.lunch), + dinner: updateMealIngredients(dayMenu.dinner), + ); + + currentMenu.value = WeeklyMenuModel( + weekId: currentMenu.value!.weekId, + startDate: currentMenu.value!.startDate, + dailyMenus: updatedMenus, + ); + + _saveToHive(); + } + + List getShoppingList() { + if (currentMenu.value == null) return []; + + final ingredientMap = {}; + + for (final dayMenu in currentMenu.value!.dailyMenus.values) { + for (final meal in [dayMenu.breakfast, dayMenu.lunch, dayMenu.dinner]) { + if (meal == null) continue; + + for (final ingredient in meal.ingredients) { + final key = '${ingredient.name}_${ingredient.unit}'; + if (ingredientMap.containsKey(key)) { + // 合并相同食材 + final existing = ingredientMap[key]!; + final newAmount = (double.tryParse(existing.amount) ?? 0) + + (double.tryParse(ingredient.amount) ?? 0); + ingredientMap[key] = ShoppingIngredient( + name: existing.name, + amount: newAmount.toString(), + unit: existing.unit, + checked: existing.checked && ingredient.checked, + ); + } else { + ingredientMap[key] = ingredient; + } + } + } + } + + return ingredientMap.values.toList(); + } + + void clearWeek() { + _createNewWeekMenu(); + ToastService.show(message: '已清空本周菜单 🗑️'); + } + + String _getMealTypeName(String mealType) { + switch (mealType) { + case 'breakfast': + return '早餐'; + case 'lunch': + return '午餐'; + case 'dinner': + return '晚餐'; + default: + return '餐食'; + } + } + + int get completedMeals { + if (currentMenu.value == null) return 0; + + int count = 0; + for (final dayMenu in currentMenu.value!.dailyMenus.values) { + if (dayMenu.breakfast != null) count++; + if (dayMenu.lunch != null) count++; + if (dayMenu.dinner != null) count++; + } + return count; + } + + int get totalPossibleMeals => 21; // 7天 x 3餐 +} diff --git a/lib/src/models/api_response.dart b/lib/src/models/api_response.dart index 90a19ec..56bccb8 100644 --- a/lib/src/models/api_response.dart +++ b/lib/src/models/api_response.dart @@ -73,7 +73,22 @@ class PaginatedData { ) { final list = json['list'] as List? ?? json['items'] as List? ?? []; return PaginatedData( - items: list.map((e) => fromJsonT(e as Map)).toList(), + items: list + .map((e) { + if (e is Map) { + return fromJsonT(e); + } + if (e is Map) { + try { + return fromJsonT(Map.from(e)); + } catch (_) { + return null; + } + } + return null; + }) + .whereType() + .toList(), total: json['total'] as int? ?? 0, page: json['page'] as int? ?? 1, pageSize: json['page_size'] as int? ?? json['limit'] as int? ?? 20, diff --git a/lib/src/models/feed_item_model.dart b/lib/src/models/feed_item_model.dart index 0d6e7de..2e3b53d 100644 --- a/lib/src/models/feed_item_model.dart +++ b/lib/src/models/feed_item_model.dart @@ -42,9 +42,7 @@ class FeedItemModel { json['category_name'] as String? ?? json['cate_name'] as String?, categoryId: json['category_id'] as int? ?? json['cate_id'] as int?, tags: _parseTags(json['tags']), - statistics: json['statistics'] != null - ? FeedStatistics.fromJson(json['statistics'] as Map) - : null, + statistics: _parseStatistics(json['statistics']), createdAt: json['created_at'] as String? ?? json['post_time'] as String?, feedType: json['feed_type'] as String?, mdhwScore: _toDouble(json['mdhw_score']), @@ -89,6 +87,20 @@ class FeedItemModel { if (v is String) return double.tryParse(v); return null; } + + static FeedStatistics? _parseStatistics(dynamic v) { + if (v is Map) { + return FeedStatistics.fromJson(v); + } + if (v is Map) { + try { + return FeedStatistics.fromJson(Map.from(v)); + } catch (_) { + return null; + } + } + return null; + } } class FeedTagItem { diff --git a/lib/src/models/recipe/ingredient_model.dart b/lib/src/models/recipe/ingredient_model.dart index f48c13f..4732f0b 100644 --- a/lib/src/models/recipe/ingredient_model.dart +++ b/lib/src/models/recipe/ingredient_model.dart @@ -1,5 +1,6 @@ // 2026-04-09 | IngredientModel | 食材数据模型 | 对齐api.php?act=unified_* type=ingredient返回字段 // 2026-04-10 | API v2.0.0: api_unified.php → api.php?act=unified_* +// 2026-04-11 | 新增introduction/usage_tip/effect/guidance字段,对齐act=ingredient_detail返回 class IngredientModel { final int id; final String name; @@ -11,6 +12,12 @@ class IngredientModel { final String? season; final String? nutrition; final IngredientStatistics? statistics; + final String? introduction; + final String? usageTip; + final String? effect; + final String? guidance; + final List allergen; + final List allergenType; const IngredientModel({ required this.id, @@ -23,6 +30,12 @@ class IngredientModel { this.season, this.nutrition, this.statistics, + this.introduction, + this.usageTip, + this.effect, + this.guidance, + this.allergen = const [], + this.allergenType = const [], }); String get displayImage => image ?? '🥬'; @@ -39,9 +52,13 @@ class IngredientModel { tags: _parseStringList(json['tags']), season: json['season'] as String?, nutrition: json['nutrition'] as String?, - statistics: json['statistics'] != null - ? IngredientStatistics.fromJson(json['statistics'] as Map) - : null, + statistics: _parseStatistics(json['statistics']), + introduction: _parseStringField(json['introduction']), + usageTip: _parseStringField(json['usage_tip']), + effect: _parseStringField(json['effect']), + guidance: _parseStringField(json['guidance']), + allergen: _parseStringList(json['allergen']), + allergenType: _parseStringList(json['allergen_type']), ); } @@ -49,6 +66,26 @@ class IngredientModel { if (value is List) return value.map((e) => e.toString()).toList(); return []; } + + static String? _parseStringField(dynamic v) { + if (v == null) return null; + if (v is String) return v.isEmpty ? null : v; + return null; + } + + static IngredientStatistics? _parseStatistics(dynamic v) { + if (v is Map) { + return IngredientStatistics.fromJson(v); + } + if (v is Map) { + try { + return IngredientStatistics.fromJson(Map.from(v)); + } catch (_) { + return null; + } + } + return null; + } } class IngredientStatistics { diff --git a/lib/src/models/recipe/recipe_model.dart b/lib/src/models/recipe/recipe_model.dart index 4228a99..831a30e 100644 --- a/lib/src/models/recipe/recipe_model.dart +++ b/lib/src/models/recipe/recipe_model.dart @@ -1,6 +1,7 @@ // 2026-04-09 | RecipeModel | 菜谱数据模型 | 对齐api.php返回字段结构 // 2026-04-10 | API v2.0.0: 新增 code/allergens/meta 字段,增强 ingredients 分类结构(main/auxiliary/seasoning) // 2026-04-11 | 新增 author/categoryHierarchy 字段,增强 IngredientDetail(别名/介绍/营养/指导/功效) +// 2026-04-11 | 新增 pic_id 字段,用于图片资源关联(替代 recipeId 构建图片URL) class RecipeModel { final int id; final String title; @@ -19,8 +20,10 @@ class RecipeModel { final RecipeMeta? meta; final List allergens; final String? code; + final int? picId; final String? createdAt; final String? updatedAt; + final int? status; const RecipeModel({ required this.id, @@ -40,8 +43,10 @@ class RecipeModel { this.meta, this.allergens = const [], this.code, + this.picId, this.createdAt, this.updatedAt, + this.status, }); String get displayImage => cover ?? '🍽️'; @@ -49,6 +54,7 @@ class RecipeModel { bool get hasCover => cover != null && cover!.isNotEmpty; bool get hasAllergens => allergens.isNotEmpty; bool get hasCode => code != null && code!.isNotEmpty; + int get effectivePicId => picId ?? id; static int _parseInt(dynamic value, [int defaultValue = 0]) { if (value == null) return defaultValue; @@ -58,6 +64,58 @@ class RecipeModel { return defaultValue; } + static String? _parseTimestamp(dynamic value) { + if (value == null) return null; + int timestamp; + if (value is int) { + timestamp = value; + } else if (value is String) { + timestamp = int.tryParse(value) ?? 0; + } else if (value is double) { + timestamp = value.toInt(); + } else { + return null; + } + if (timestamp == 0) return null; + + try { + int ms; + final digitCount = timestamp.toString().length; + + if (digitCount >= 13) { + ms = timestamp; + } else if (digitCount == 10) { + ms = timestamp * 1000; + } else if (digitCount == 12) { + ms = timestamp * 10; + } else { + ms = timestamp * 1000; + } + + final dateTime = DateTime.fromMillisecondsSinceEpoch(ms); + final year = dateTime.year; + + if (year < 2000 || year > 2100) { + if (digitCount == 12) { + final correctedMs = timestamp * 100; + final correctedDate = DateTime.fromMillisecondsSinceEpoch( + correctedMs, + ); + if (correctedDate.year >= 2000 && correctedDate.year <= 2100) { + return '${correctedDate.year}-${correctedDate.month.toString().padLeft(2, '0')}-${correctedDate.day.toString().padLeft(2, '0')} ' + '${correctedDate.hour.toString().padLeft(2, '0')}:${correctedDate.minute.toString().padLeft(2, '0')}'; + } + } + return null; + } + + return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} ' + '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'; + } catch (_) { + return null; + } + } + static String? _parseStringOrNull(dynamic value) { if (value == null) return null; if (value is String) return value.isEmpty ? null : value; @@ -117,10 +175,12 @@ class RecipeModel { meta: _parseMeta(json['meta']), allergens: _parseAllergens(json['allergens']), code: _parseStringOrNull(json['code']), - createdAt: - _parseStringOrNull(json['created_at']) ?? - _parseStringOrNull(json['post_time']), - updatedAt: _parseStringOrNull(json['updated_at']), + picId: _parseInt(json['pic_id'] ?? json['picId']), + createdAt: _parseTimestamp( + json['create_time'] ?? json['created_at'] ?? json['post_time'], + ), + updatedAt: _parseTimestamp(json['update_time'] ?? json['updated_at']), + status: _parseInt(json['status']), ); } @@ -129,6 +189,13 @@ class RecipeModel { if (json is Map) { return NutritionInfo.fromJson(json); } + if (json is Map) { + try { + return NutritionInfo.fromJson(Map.from(json)); + } catch (_) { + return null; + } + } if (json is List) { return NutritionInfo.fromList(json); } @@ -144,13 +211,24 @@ class RecipeModel { } static CategorizedIngredients? _parseCategorizedIngredients(dynamic json) { - if (json is! Map) return null; - if (!json.containsKey('main') && - !json.containsKey('auxiliary') && - !json.containsKey('seasoning')) { + if (json == null) return null; + Map? map; + if (json is Map) { + map = json; + } else if (json is Map) { + try { + map = Map.from(json); + } catch (_) { + return null; + } + } + if (map == null) return null; + if (!map.containsKey('main') && + !map.containsKey('auxiliary') && + !map.containsKey('seasoning')) { return null; } - return CategorizedIngredients.fromJson(json); + return CategorizedIngredients.fromJson(map); } static List _parseTags(dynamic tagsJson) { @@ -171,7 +249,20 @@ class RecipeModel { if (ingredientsJson == null) return []; if (ingredientsJson is List) { return ingredientsJson - .map((e) => IngredientItem.fromJson(e as Map)) + .map((e) { + if (e is Map) { + return IngredientItem.fromJson(e); + } + if (e is Map) { + try { + return IngredientItem.fromJson(Map.from(e)); + } catch (_) { + return null; + } + } + return null; + }) + .whereType() .toList(); } if (ingredientsJson is Map) { @@ -182,6 +273,12 @@ class RecipeModel { for (final item in list) { if (item is Map) { result.add(IngredientItem.fromJson(item)); + } else if (item is Map) { + try { + result.add( + IngredientItem.fromJson(Map.from(item)), + ); + } catch (_) {} } } } @@ -196,6 +293,13 @@ class RecipeModel { if (json is Map) { return RecipeStatistics.fromJson(json); } + if (json is Map) { + try { + return RecipeStatistics.fromJson(Map.from(json)); + } catch (_) { + return null; + } + } return null; } @@ -204,22 +308,43 @@ class RecipeModel { if (json is Map) { return RecipeMeta.fromJson(json); } + if (json is Map) { + try { + return RecipeMeta.fromJson(Map.from(json)); + } catch (_) { + return null; + } + } return null; } static List _parseCategoryHierarchy(dynamic json) { try { - if (json is! Map) return []; - final hierarchy = json['hierarchy']; + if (json is! Map && json is! Map) return []; + final map = json is Map + ? json + : Map.from(json as Map); + final hierarchy = map['hierarchy']; if (hierarchy is! List) return []; return hierarchy - .whereType>() .map((e) { - try { - return CategoryHierarchyItem.fromJson(e); - } catch (_) { - return null; + if (e is Map) { + try { + return CategoryHierarchyItem.fromJson(e); + } catch (_) { + return null; + } } + if (e is Map) { + try { + return CategoryHierarchyItem.fromJson( + Map.from(e), + ); + } catch (_) { + return null; + } + } + return null; }) .whereType() .toList(); @@ -229,8 +354,17 @@ class RecipeModel { } static RecipeAuthor? _parseAuthor(dynamic json) { - if (json is! Map) return null; - return RecipeAuthor.fromJson(json); + if (json is Map) { + return RecipeAuthor.fromJson(json); + } + if (json is Map) { + try { + return RecipeAuthor.fromJson(Map.from(json)); + } catch (_) { + return null; + } + } + return null; } } @@ -286,12 +420,24 @@ class IngredientItem { amount: _parseStringField(json['amount']), unit: _parseStringField(json['unit']), category: _parseStringField(json['category']), - detail: json['detail'] != null - ? IngredientDetail.fromJson(json['detail'] as Map) - : null, + detail: _parseIngredientDetail(json['detail']), ); } + static IngredientDetail? _parseIngredientDetail(dynamic v) { + if (v is Map) { + return IngredientDetail.fromJson(v); + } + if (v is Map) { + try { + return IngredientDetail.fromJson(Map.from(v)); + } catch (_) { + return null; + } + } + return null; + } + static int? _parseIntField(dynamic v) { if (v == null) return null; if (v is int) return v; @@ -390,7 +536,20 @@ class CategorizedIngredients { static List _parseList(dynamic json) { if (json is! List) return []; return json - .map((e) => IngredientItem.fromJson(e as Map)) + .map((e) { + if (e is Map) { + return IngredientItem.fromJson(e); + } + if (e is Map) { + try { + return IngredientItem.fromJson(Map.from(e)); + } catch (_) { + return null; + } + } + return null; + }) + .whereType() .toList(); } @@ -428,8 +587,20 @@ class NutritionInfo { factory NutritionInfo.fromList(List list) { final items = list - .whereType>() - .map((e) => NutritionItem.fromJson(e)) + .map((e) { + if (e is Map) { + return NutritionItem.fromJson(e); + } + if (e is Map) { + try { + return NutritionItem.fromJson(Map.from(e)); + } catch (_) { + return null; + } + } + return null; + }) + .whereType() .toList(); double? calories; diff --git a/lib/src/models/weekly_menu_model.dart b/lib/src/models/weekly_menu_model.dart new file mode 100644 index 0000000..6f8133c --- /dev/null +++ b/lib/src/models/weekly_menu_model.dart @@ -0,0 +1,161 @@ +/* + * 文件: weekly_menu_model.dart + * 名称: 每周菜单模型 + * 作用: 每周菜单规划数据模型 + * 创建: 2026-04-11 + * 更新: 2026-04-11 初始实现 + */ + +class WeeklyMenuModel { + final String weekId; + final DateTime startDate; + final Map dailyMenus; + + const WeeklyMenuModel({ + required this.weekId, + required this.startDate, + required this.dailyMenus, + }); + + factory WeeklyMenuModel.fromJson(Map json) { + final dailyMenus = {}; + if (json['daily_menus'] != null) { + (json['daily_menus'] as Map).forEach((key, value) { + dailyMenus[key] = DayMenu.fromJson(value as Map); + }); + } + + return WeeklyMenuModel( + weekId: json['week_id'] ?? '', + startDate: DateTime.parse(json['start_date'] ?? DateTime.now().toIso8601String()), + dailyMenus: dailyMenus, + ); + } + + Map toJson() { + final dailyMenusJson = {}; + dailyMenus.forEach((key, value) { + dailyMenusJson[key] = value.toJson(); + }); + + return { + 'week_id': weekId, + 'start_date': startDate.toIso8601String(), + 'daily_menus': dailyMenusJson, + }; + } + + String get weekLabel { + final endDate = startDate.add(const Duration(days: 6)); + return '${startDate.month}/${startDate.day} - ${endDate.month}/${endDate.day}'; + } +} + +class DayMenu { + final String dateKey; + final MealItem? breakfast; + final MealItem? lunch; + final MealItem? dinner; + + const DayMenu({ + required this.dateKey, + this.breakfast, + this.lunch, + this.dinner, + }); + + factory DayMenu.fromJson(Map json) { + return DayMenu( + dateKey: json['date_key'] ?? '', + breakfast: json['breakfast'] != null ? MealItem.fromJson(json['breakfast']) : null, + lunch: json['lunch'] != null ? MealItem.fromJson(json['lunch']) : null, + dinner: json['dinner'] != null ? MealItem.fromJson(json['dinner']) : null, + ); + } + + Map toJson() { + return { + 'date_key': dateKey, + 'breakfast': breakfast?.toJson(), + 'lunch': lunch?.toJson(), + 'dinner': dinner?.toJson(), + }; + } + + bool get hasAnyMeal => breakfast != null || lunch != null || dinner != null; + int get mealCount => [breakfast, lunch, dinner].where((m) => m != null).length; +} + +class MealItem { + final int recipeId; + final String recipeTitle; + final String? cover; + final List ingredients; + + const MealItem({ + required this.recipeId, + required this.recipeTitle, + this.cover, + required this.ingredients, + }); + + factory MealItem.fromJson(Map json) { + final ingredients = []; + if (json['ingredients'] != null) { + ingredients.addAll( + (json['ingredients'] as List) + .map((e) => ShoppingIngredient.fromJson(e as Map)) + ); + } + + return MealItem( + recipeId: json['recipe_id'] ?? 0, + recipeTitle: json['recipe_title'] ?? '', + cover: json['cover'], + ingredients: ingredients, + ); + } + + Map toJson() { + return { + 'recipe_id': recipeId, + 'recipe_title': recipeTitle, + 'cover': cover, + 'ingredients': ingredients.map((e) => e.toJson()).toList(), + }; + } +} + +class ShoppingIngredient { + final String name; + final String amount; + final String unit; + bool checked; + + ShoppingIngredient({ + required this.name, + required this.amount, + required this.unit, + this.checked = false, + }); + + factory ShoppingIngredient.fromJson(Map json) { + return ShoppingIngredient( + name: json['name'] ?? '', + amount: json['amount'] ?? '', + unit: json['unit'] ?? '', + checked: json['checked'] ?? false, + ); + } + + Map toJson() { + return { + 'name': name, + 'amount': amount, + 'unit': unit, + 'checked': checked, + }; + } + + String get display => '$amount $unit $name'; +} diff --git a/lib/src/pages/discover/category_browse_page.dart b/lib/src/pages/discover/category_browse_page.dart index d84440e..cc05a5e 100644 --- a/lib/src/pages/discover/category_browse_page.dart +++ b/lib/src/pages/discover/category_browse_page.dart @@ -17,8 +17,14 @@ import 'package:mom_kitchen/src/widgets/recipe_image.dart'; class CategoryBrowsePage extends StatefulWidget { final CategoryModel? category; final String title; + final bool loadRecipesDirectly; - const CategoryBrowsePage({super.key, this.category, this.title = '分类浏览'}); + const CategoryBrowsePage({ + super.key, + this.category, + this.title = '分类浏览', + this.loadRecipesDirectly = false, + }); @override State createState() => _CategoryBrowsePageState(); @@ -40,13 +46,21 @@ class _CategoryBrowsePageState extends State { Future _loadData() async { setState(() => _isLoading = true); try { - if (widget.category != null && widget.category!.children.isNotEmpty) { + if (widget.loadRecipesDirectly && widget.category != null) { + await _loadRecipes(widget.category!.id); + _categories = []; + } else if (widget.category != null && + widget.category!.children.isNotEmpty) { _categories = widget.category!.children; + if (_categories.isNotEmpty) { + await _loadRecipes(_categories.first.id); + _selectedSubCategory = _categories.first; + } } else { _categories = await _repo.fetchCategories(); - } - if (_categories.isNotEmpty && widget.category != null) { - await _loadRecipes(_categories.first.id); + if (_categories.isNotEmpty && widget.category != null) { + await _loadRecipes(_categories.first.id); + } } } catch (e) { debugPrint('CategoryBrowsePage load error: $e'); @@ -154,6 +168,7 @@ class _CategoryBrowsePageState extends State { bool isTopLevel = false, }) { final hasChildren = cat.children.isNotEmpty; + final hasRecipes = cat.count != null && cat.count! > 0; return GestureDetector( onTap: () { @@ -162,6 +177,11 @@ class _CategoryBrowsePageState extends State { '/category-browse', arguments: {'category': cat, 'title': cat.name}, ); + } else if (hasRecipes) { + setState(() { + _selectedSubCategory = cat; + }); + _loadRecipes(cat.id); } else { Get.toNamed( '/category-browse', @@ -357,6 +377,7 @@ class _CategoryBrowsePageState extends State { borderRadius: DesignTokens.borderRadiusMd, child: RecipeImage( recipeId: recipe.id, + picId: recipe.picId, coverUrl: recipe.cover, width: 60, height: 60, diff --git a/lib/src/pages/discover/discover_page.dart b/lib/src/pages/discover/discover_page.dart index 0331eb9..ae4f93c 100644 --- a/lib/src/pages/discover/discover_page.dart +++ b/lib/src/pages/discover/discover_page.dart @@ -15,7 +15,10 @@ import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; import 'package:mom_kitchen/src/widgets/glass/glass_search_bar.dart'; import 'package:mom_kitchen/src/widgets/glass/glass_segmented_control.dart'; import 'package:mom_kitchen/src/controllers/feed/hot_controller.dart'; +import 'package:mom_kitchen/src/controllers/favorites_controller.dart'; import 'package:mom_kitchen/src/controllers/shopping_list_controller.dart'; +import 'package:mom_kitchen/src/models/feed_item_model.dart'; +import 'package:mom_kitchen/src/services/ui/toast_service.dart'; import 'package:mom_kitchen/src/repositories/hot_repository.dart' as repo; class DiscoverPage extends StatefulWidget { @@ -27,9 +30,11 @@ class DiscoverPage extends StatefulWidget { class _DiscoverPageState extends State { int _segmentIndex = 0; + int _recommendTypeIndex = 0; late HotController _hotController; final RecipeRepository _recipeRepo = RecipeRepository(); List _topCategories = []; + List _ingredientCategories = []; bool _isLoadingCategories = true; @override @@ -42,9 +47,13 @@ class _DiscoverPageState extends State { Future _loadCategories() async { try { final categories = await _recipeRepo.fetchCategories(); + final ingredientCategories = await _recipeRepo.fetchCategories( + type: 'ingredient', + ); if (mounted) { setState(() { _topCategories = categories; + _ingredientCategories = ingredientCategories; _isLoadingCategories = false; }); } @@ -304,81 +313,157 @@ class _DiscoverPageState extends State { padding: const EdgeInsets.only( bottom: DesignTokens.space2 + 2, ), - child: GestureDetector( - onTap: () { - Get.toNamed('/recipe-detail', arguments: '${recipe.id}'); + child: Dismissible( + key: ValueKey('hot_${recipe.id}_$index'), + direction: DismissDirection.horizontal, + confirmDismiss: (direction) async { + if (direction == DismissDirection.endToStart) { + Get.toNamed( + '/recipe-detail', + arguments: '${recipe.id}', + ); + return false; + } else { + return true; + } }, - child: Container( - padding: const EdgeInsets.all(DesignTokens.space3), + onDismissed: (direction) { + if (direction == DismissDirection.startToEnd) { + _showQuickActions(recipe, isDark); + } + }, + background: Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.only(left: 20), decoration: BoxDecoration( - color: isDark - ? DarkDesignTokens.card - : DesignTokens.card, + color: DesignTokens.primary, borderRadius: DesignTokens.borderRadiusLg, - boxShadow: DesignTokens.shadowsSm, ), child: Row( + mainAxisSize: MainAxisSize.min, children: [ - Container( - width: 28, - height: 28, - decoration: BoxDecoration( - color: index < 3 - ? DesignTokens.orange.withValues(alpha: 0.15) - : DesignTokens.text3.withValues(alpha: 0.1), - borderRadius: DesignTokens.borderRadiusSm, + Icon( + CupertinoIcons.heart_fill, + color: CupertinoColors.white, + ), + const SizedBox(width: 8), + Text( + '收藏', + style: TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.w600, ), - child: Center( - child: Text( - '${index + 1}', - style: TextStyle( - fontSize: DesignTokens.fontSm, - fontWeight: FontWeight.w700, - color: index < 3 - ? DesignTokens.orange - : (isDark - ? DarkDesignTokens.text2 - : DesignTokens.text2), + ), + ], + ), + ), + secondaryBackground: Container( + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + decoration: BoxDecoration( + color: DesignTokens.green, + borderRadius: DesignTokens.borderRadiusLg, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + '查看详情', + style: TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 8), + Icon( + CupertinoIcons.eye, + color: CupertinoColors.white, + ), + ], + ), + ), + child: GestureDetector( + onTap: () { + Get.toNamed( + '/recipe-detail', + arguments: '${recipe.id}', + ); + }, + child: Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.card + : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Row( + children: [ + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: index < 3 + ? DesignTokens.orange.withValues( + alpha: 0.15, + ) + : DesignTokens.text3.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Center( + child: Text( + '${index + 1}', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w700, + color: index < 3 + ? DesignTokens.orange + : (isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2), + ), ), ), ), - ), - const SizedBox(width: DesignTokens.space3), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - recipe.name, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w500, - color: isDark - ? DarkDesignTokens.text1 - : DesignTokens.text1, + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + recipe.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), ), - ), - const SizedBox(height: 2), - Text( - '${_hotController.sortByName}: ${recipe.count}', - style: TextStyle( - fontSize: DesignTokens.fontSm, - color: isDark - ? DarkDesignTokens.text2 - : DesignTokens.text2, + const SizedBox(height: 2), + Text( + '${_hotController.sortByName}: ${recipe.count}', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), ), - ), - ], + ], + ), ), - ), - Icon( - CupertinoIcons.chevron_forward, - size: 16, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, - ), - ], + Icon( + CupertinoIcons.chevron_forward, + size: 16, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ], + ), ), ), ), @@ -472,12 +557,106 @@ class _DiscoverPageState extends State { ); } + void _showQuickActions(repo.HotItem recipe, bool isDark) { + try { + final favoritesController = Get.find(); + final feedItem = FeedItemModel( + id: recipe.id, + title: recipe.name, + cover: '', + feedType: 'recipe', + createdAt: DateTime.now().toIso8601String(), + ); + final isFav = favoritesController.isFavorited(recipe.id); + + showCupertinoModalPopup( + context: context, + builder: (ctx) => CupertinoActionSheet( + title: Text( + recipe.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + ), + ), + message: Text( + '浏览量: ${recipe.count}', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + actions: [ + CupertinoActionSheetAction( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + isFav ? CupertinoIcons.heart_fill : CupertinoIcons.heart, + color: isFav ? DesignTokens.red : DesignTokens.primary, + ), + const SizedBox(width: 8), + Text(isFav ? '取消收藏' : '收藏菜谱'), + ], + ), + onPressed: () { + Navigator.pop(ctx); + favoritesController.toggleFavorite(feedItem); + ToastService.show(message: isFav ? '已取消收藏 ❤️' : '已收藏 ❤️'); + }, + ), + CupertinoActionSheetAction( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(CupertinoIcons.eye, color: DesignTokens.green), + const SizedBox(width: 8), + Text('查看详情'), + ], + ), + onPressed: () { + Navigator.pop(ctx); + Get.toNamed('/recipe-detail', arguments: '${recipe.id}'); + }, + ), + CupertinoActionSheetAction( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(CupertinoIcons.share_up, color: DesignTokens.orange), + const SizedBox(width: 8), + Text('分享'), + ], + ), + onPressed: () { + Navigator.pop(ctx); + ToastService.show(message: '分享功能开发中 📤'); + }, + ), + ], + cancelButton: CupertinoActionSheetAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(ctx), + child: const Text('取消'), + ), + ), + ); + } catch (e) { + debugPrint('DiscoverPage _showQuickActions error: $e'); + } + } + Widget _buildRecommendSection(bool isDark) { if (_isLoadingCategories) { return const Center(child: CupertinoActivityIndicator()); } - if (_topCategories.isEmpty) { + final categories = _recommendTypeIndex == 0 + ? _topCategories + : _ingredientCategories; + final typeLabel = _recommendTypeIndex == 0 ? '菜谱' : '食材'; + + if (categories.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -485,7 +664,7 @@ class _DiscoverPageState extends State { const Text('📂', style: TextStyle(fontSize: 56)), const SizedBox(height: DesignTokens.space4), Text( - '暂无分类数据', + '暂无$typeLabel分类数据', style: TextStyle( fontSize: DesignTokens.fontLg, color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, @@ -496,114 +675,153 @@ class _DiscoverPageState extends State { ); } - return GridView.builder( - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space4, - vertical: DesignTokens.space2, - ), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: DesignTokens.space3, - crossAxisSpacing: DesignTokens.space3, - childAspectRatio: 1.1, - ), - itemCount: _topCategories.length, - itemBuilder: (context, index) { - final cat = _topCategories[index]; - final hasChildren = cat.children.isNotEmpty; - - return GestureDetector( - onTap: () { - Get.toNamed( - '/category-browse', - arguments: {'category': cat, 'title': cat.name}, - ); - }, - child: Container( - decoration: BoxDecoration( - color: isDark ? DarkDesignTokens.card : DesignTokens.card, - borderRadius: DesignTokens.borderRadiusLg, - border: Border.all( - color: isDark - ? DarkDesignTokens.glassBorder - : DesignTokens.text3.withValues(alpha: 0.1), - ), - boxShadow: DesignTokens.shadowsSm, + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space4), + child: GlassSegmentedControl( + segments: const [ + GlassSegment(label: '📖 菜谱'), + GlassSegment(label: '🥬 食材'), + ], + selectedIndex: _recommendTypeIndex, + onChanged: (i) { + setState(() => _recommendTypeIndex = i); + }, + ), + ), + const SizedBox(height: DesignTokens.space3), + Expanded( + child: GridView.builder( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space2, ), - child: Stack( - children: [ - Positioned( - right: -10, - bottom: -10, - child: Text( - cat.displayIcon, - style: TextStyle( - fontSize: 72, - color: - (isDark - ? DarkDesignTokens.primary - : DesignTokens.primary) - .withValues(alpha: 0.08), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: DesignTokens.space3, + crossAxisSpacing: DesignTokens.space3, + childAspectRatio: 1.1, + ), + itemCount: categories.length, + itemBuilder: (context, index) { + final cat = categories[index]; + final hasChildren = cat.children.isNotEmpty; + + return GestureDetector( + onTap: () { + final hasChildren = cat.children.isNotEmpty; + final hasRecipes = cat.count != null && cat.count! > 0; + + if (hasChildren) { + Get.toNamed( + '/category-browse', + arguments: {'category': cat, 'title': cat.name}, + ); + } else if (hasRecipes) { + Get.toNamed( + '/category-browse', + arguments: { + 'category': cat, + 'title': '${cat.name} (${cat.count}道$typeLabel)', + 'loadRecipesDirectly': true, + }, + ); + } else { + Get.toNamed( + '/category-browse', + arguments: {'category': cat, 'title': cat.name}, + ); + } + }, + child: Container( + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + border: Border.all( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.1), ), + boxShadow: DesignTokens.shadowsSm, ), - ), - Padding( - padding: const EdgeInsets.all(DesignTokens.space3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Stack( children: [ - Text( - cat.displayIcon, - style: const TextStyle(fontSize: 32), - ), - const SizedBox(height: DesignTokens.space2), - Text( - cat.name, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isDark - ? DarkDesignTokens.text1 - : DesignTokens.text1, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 2), - Text( - hasChildren - ? '${cat.children.length} 个子类' - : (cat.count != null && cat.count! > 0 - ? '${cat.count} 道菜谱' - : '浏览'), - style: TextStyle( - fontSize: DesignTokens.fontXs, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, - ), - ), - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Icon( - CupertinoIcons.chevron_forward, - size: 16, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, + Positioned( + right: -10, + bottom: -10, + child: Text( + cat.displayIcon, + style: TextStyle( + fontSize: 72, + color: + (isDark + ? DarkDesignTokens.primary + : DesignTokens.primary) + .withValues(alpha: 0.08), ), - ], + ), + ), + Padding( + padding: const EdgeInsets.all(DesignTokens.space3), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + cat.displayIcon, + style: const TextStyle(fontSize: 32), + ), + const SizedBox(height: DesignTokens.space2), + Text( + cat.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + hasChildren + ? '${cat.children.length} 个子类' + : (cat.count != null && cat.count! > 0 + ? '${cat.count} 道$typeLabel' + : '浏览'), + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + CupertinoIcons.chevron_forward, + size: 16, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ], + ), + ], + ), ), ], ), ), - ], - ), + ); + }, ), - ); - }, + ), + ], ); } } diff --git a/lib/src/pages/home/home_page.dart b/lib/src/pages/home/home_page.dart index 2698514..734d20f 100644 --- a/lib/src/pages/home/home_page.dart +++ b/lib/src/pages/home/home_page.dart @@ -1,12 +1,15 @@ /* * 文件: home_page.dart * 名称: 首页 - * 作用: iOS 26 风格首页,横向滑动卡片布局,点击显示菜品详情 + * 作用: iOS风格首页,横向滑动卡片布局,点击显示菜品详情 * 更新: 2026-04-10 添加营养追踪仪表盘卡片 - * 更新: 2026-04-10 添加骨架屏+超时保护+缓存优先策略 + * 更新: 2026-04-10 添加骨架屏加载优化 + * 更新: 2026-04-11 添加按时段问候语功能 + * 更新: 2026-04-11 添加"为你推荐"Tab,集成AI菜谱推荐 */ import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; @@ -17,6 +20,7 @@ import 'package:mom_kitchen/src/widgets/base/skeleton_loader.dart'; import 'package:mom_kitchen/src/services/ui/toast_service.dart'; import 'package:mom_kitchen/src/controllers/meal_record_controller.dart'; import 'package:mom_kitchen/src/widgets/recipe_image.dart'; +import 'package:mom_kitchen/src/services/recommendation_service.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -28,14 +32,19 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { final RecipeRepository _recipeRepository = RecipeRepository(); final RxList _recipes = [].obs; + final RxList _recommendedRecipes = [].obs; final RxBool _isLoading = true.obs; + final RxBool _isLoadingRecommendations = false.obs; final RxString _error = ''.obs; + bool _showMessage = false; + String _currentTab = 'today'; // 'today' or 'recommended' @override void initState() { super.initState(); _loadRecipes(); _initNutritionController(); + _initRecommendationService(); } void _initNutritionController() { @@ -48,6 +57,29 @@ class _HomePageState extends State { } } + void _initRecommendationService() { + try { + if (!Get.isRegistered()) { + Get.put(RecommendationService(), permanent: true); + } + } catch (e) { + debugPrint('Init recommendation service error: $e'); + } + } + + Future _loadRecommendations() async { + _isLoadingRecommendations.value = true; + try { + final recommendationService = Get.find(); + final recommendations = await recommendationService.getPersonalizedRecommendations(limit: 10); + _recommendedRecipes.value = recommendations; + } catch (e) { + debugPrint('Load recommendations error: $e'); + } finally { + _isLoadingRecommendations.value = false; + } + } + Future _loadRecipes({bool refresh = false}) async { if (refresh) { _isLoading.value = true; @@ -101,24 +133,61 @@ class _HomePageState extends State { Widget build(BuildContext context) { final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; - return CupertinoPageScaffold( - backgroundColor: isDark - ? DarkDesignTokens.background - : DesignTokens.background, - child: SafeArea( - child: Column( - children: [ - _buildHeader(isDark), - const SizedBox(height: DesignTokens.space3), - _buildSearchBar(isDark), - const SizedBox(height: DesignTokens.space4), - Expanded(child: Obx(() => _buildContent(isDark))), - ], - ), + return SafeArea( + child: Column( + children: [ + _buildHeader(isDark), + const SizedBox(height: DesignTokens.space3), + _buildSearchBar(isDark), + const SizedBox(height: DesignTokens.space4), + Expanded(child: Obx(() => _buildContent(isDark))), + ], ), ); } + String _getGreeting() { + final hour = DateTime.now().hour; + if (hour >= 5 && hour < 7) { + return '清晨好'; + } else if (hour >= 7 && hour < 9) { + return '早上好'; + } else if (hour >= 9 && hour < 12) { + return '上午好'; + } else if (hour >= 12 && hour < 14) { + return '中午好'; + } else if (hour >= 14 && hour < 17) { + return '下午好'; + } else if (hour >= 17 && hour < 19) { + return '傍晚好'; + } else if (hour >= 19 && hour < 22) { + return '晚上好'; + } else { + return '夜深了'; + } + } + + String _getGreetingMessage() { + final hour = DateTime.now().hour; + if (hour >= 5 && hour < 7) { + return '清晨了,呼吸新鲜空气'; + } else if (hour >= 7 && hour < 9) { + return '早上好,元气满满'; + } else if (hour >= 9 && hour < 12) { + return '上午好,努力工作'; + } else if (hour >= 12 && hour < 14) { + return '中午了,吃顿好的'; + } else if (hour >= 14 && hour < 17) { + return '下午了,喝杯咖啡'; + } else if (hour >= 17 && hour < 19) { + return '傍晚了,休息一下'; + } else if (hour >= 19 && hour < 22) { + return '晚上好,放松身心'; + } else { + return '深夜了,注意身体'; + } + } + Widget _buildHeader(bool isDark) { return Padding( padding: const EdgeInsets.symmetric( @@ -140,20 +209,60 @@ class _HomePageState extends State { const Spacer(), GestureDetector( onTap: () { - Get.toNamed(AppRoutes.search); + setState(() { + _showMessage = !_showMessage; + }); }, - child: Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: isDark ? DarkDesignTokens.card : DesignTokens.card, - borderRadius: DesignTokens.borderRadiusMd, - boxShadow: DesignTokens.shadowsSm, + child: AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + crossFadeState: _showMessage + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + firstChild: Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space3, + vertical: DesignTokens.space2, + ), + decoration: BoxDecoration( + color: + (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Text( + _getGreeting(), + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + ), + ), ), - child: Icon( - CupertinoIcons.search, - size: 18, - color: DesignTokens.dynamicPrimary, + secondChild: Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space3, + vertical: DesignTokens.space2, + ), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.card : DesignTokens.card), + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + _getGreetingMessage(), + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), ), ), ), @@ -300,19 +409,30 @@ class _HomePageState extends State { ); } + final currentRecipes = _currentTab == 'today' ? _recipes : _recommendedRecipes; + final List sliverList = [ CupertinoSliverRefreshControl( - onRefresh: () => _loadRecipes(refresh: true), + onRefresh: () async { + await _loadRecipes(refresh: true); + if (_currentTab == 'recommended') { + await _loadRecommendations(); + } + }, ), SliverToBoxAdapter(child: const NutritionDashboardCard()), const SliverToBoxAdapter(child: SizedBox(height: DesignTokens.space4)), + SliverToBoxAdapter( + child: _buildTabSwitcher(isDark), + ), + const SliverToBoxAdapter(child: SizedBox(height: DesignTokens.space3)), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space4), child: Row( children: [ Text( - '🔥 今日推荐', + _currentTab == 'today' ? '🔥 今日推荐' : '✨ 为你推荐', style: TextStyle( fontSize: DesignTokens.fontLg, fontWeight: FontWeight.w600, @@ -321,7 +441,7 @@ class _HomePageState extends State { ), const Spacer(), Text( - '${_recipes.length} 道菜谱', + '${currentRecipes.length} 道菜谱', style: TextStyle( fontSize: DesignTokens.fontSm, color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, @@ -332,23 +452,60 @@ class _HomePageState extends State { ), ), SliverToBoxAdapter( - child: SizedBox( - height: 320, - child: ListView.separated( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space4, - vertical: DesignTokens.space3, + child: Obx(() { + if (_currentTab == 'recommended' && _isLoadingRecommendations.value) { + return const SizedBox( + height: 320, + child: Center(child: CupertinoActivityIndicator()), + ); + } + + if (currentRecipes.isEmpty) { + return SizedBox( + height: 200, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + _currentTab == 'recommended' ? '🎯' : '🔥', + style: const TextStyle(fontSize: 48), + ), + const SizedBox(height: DesignTokens.space3), + Text( + _currentTab == 'recommended' + ? '根据你的偏好推荐菜谱\n请先设置偏好或浏览更多菜谱' + : '暂无推荐', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ), + ); + } + + return SizedBox( + height: 320, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space3, + ), + itemCount: currentRecipes.length, + separatorBuilder: (_, _) => + const SizedBox(width: DesignTokens.space3), + itemBuilder: (context, index) { + final recipe = currentRecipes[index]; + return _buildRecipeCard(recipe, isDark); + }, ), - itemCount: _recipes.length, - separatorBuilder: (_, _) => - const SizedBox(width: DesignTokens.space3), - itemBuilder: (context, index) { - final recipe = _recipes[index]; - return _buildRecipeCard(recipe, isDark); - }, - ), - ), + ); + }), ), SliverToBoxAdapter( child: Padding( @@ -392,12 +549,88 @@ class _HomePageState extends State { ]; return CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), - controller: ScrollController(), + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), slivers: sliverList, ); } + Widget _buildTabSwitcher(bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space4), + child: Container( + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _currentTab = 'today'; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: _currentTab == 'today' + ? (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + : CupertinoColors.transparent, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Text( + '今日推荐', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: _currentTab == 'today' + ? CupertinoColors.white + : (isDark ? DarkDesignTokens.text2 : DesignTokens.text2), + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + setState(() { + _currentTab = 'recommended'; + _loadRecommendations(); + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: _currentTab == 'recommended' + ? (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + : CupertinoColors.transparent, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Text( + '为你推荐', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: _currentTab == 'recommended' + ? CupertinoColors.white + : (isDark ? DarkDesignTokens.text2 : DesignTokens.text2), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + Widget _buildRecipeCard(RecipeModel recipe, bool isDark) { return GestureDetector( onTap: () { @@ -430,6 +663,7 @@ class _HomePageState extends State { ), child: RecipeImage( recipeId: recipe.id, + picId: recipe.picId, coverUrl: recipe.cover, fit: BoxFit.cover, width: double.infinity, diff --git a/lib/src/pages/home/recipe_detail_page.dart b/lib/src/pages/home/recipe_detail_page.dart index d821d17..745e1cd 100644 --- a/lib/src/pages/home/recipe_detail_page.dart +++ b/lib/src/pages/home/recipe_detail_page.dart @@ -1,7 +1,10 @@ // 2026-04-09 | recipe_detail_page.dart | 菜谱详情页 | 展示菜谱详细信息 // 2026-04-11 | 重构: 显示API返回的所有字段,优化iOS风格布局 +// 2026-04-11 | Bug修复: 骨架屏+优先加载策略,解决加载超5秒问题 +// 2026-04-11 | 新增: Picid显示和复制功能 import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/controllers/favorites_controller.dart'; import 'package:mom_kitchen/src/controllers/feed/action_controller.dart'; @@ -15,6 +18,7 @@ import 'package:mom_kitchen/src/models/shopping_item_model.dart'; import 'package:mom_kitchen/src/services/ui/toast_service.dart'; import 'package:mom_kitchen/src/utils/common_utils.dart'; import 'package:mom_kitchen/src/widgets/recipe_image.dart'; +import 'package:mom_kitchen/src/widgets/base/skeleton_loader.dart'; class RecipeDetailPage extends StatefulWidget { final String recipeId; @@ -25,7 +29,8 @@ class RecipeDetailPage extends StatefulWidget { State createState() => _RecipeDetailPageState(); } -class _RecipeDetailPageState extends State { +class _RecipeDetailPageState extends State + with SingleTickerProviderStateMixin { final AllergenChecker _allergenChecker = AllergenChecker(); final RecipeRepository _recipeRepository = RecipeRepository(); @@ -34,22 +39,51 @@ class _RecipeDetailPageState extends State { bool _isFavorite = false; int _likeCount = 0; int _viewCount = 0; + bool _loadTimeout = false; late final ActionController _actionController; late final FavoritesController _favoritesController; + static const Duration _loadTimeoutDuration = Duration(seconds: 8); + @override void initState() { super.initState(); - _actionController = Get.find(); - _favoritesController = Get.find(); - _loadRecipe(); + try { + _actionController = Get.find(); + } catch (e) { + debugPrint('ActionController 获取失败: $e'); + } + try { + _favoritesController = Get.find(); + } catch (e) { + debugPrint('FavoritesController 获取失败: $e'); + } + _loadRecipeWithTimeout(); } - Future _loadRecipe() async { + Future _loadRecipeWithTimeout() async { + setState(() { + _isLoading = true; + _loadTimeout = false; + }); + + Future.delayed(_loadTimeoutDuration, () { + if (mounted && _isLoading) { + setState(() => _loadTimeout = true); + debugPrint('⏰ 详情页加载超时 (${_loadTimeoutDuration.inSeconds}秒)'); + } + }); + try { final recipeId = int.tryParse(widget.recipeId) ?? 0; - final recipe = await _recipeRepository.fetchDetail(recipeId); + final recipe = await _recipeRepository.fetchFull( + recipeId, + viewnums: true, + ); + debugPrint('🔍 菜谱数据加载成功: id=${recipe.id}, title=${recipe.title}'); + debugPrint('🔍 PicId原始值: ${recipe.picId}, cover=${recipe.cover}'); + debugPrint('🔍 Recipe其他字段: code=${recipe.code}, status=${recipe.status}'); if (mounted) { setState(() { _recipe = recipe; @@ -62,7 +96,9 @@ class _RecipeDetailPageState extends State { } } catch (e) { if (mounted) { - setState(() => _isLoading = false); + setState(() { + _isLoading = false; + }); Get.snackbar('错误', '加载菜谱失败: $e'); } } @@ -70,8 +106,12 @@ class _RecipeDetailPageState extends State { void _checkFavorite() { if (_recipe != null) { - final isFav = _favoritesController.isFavorited(_recipe!.id); - if (mounted) setState(() => _isFavorite = isFav); + try { + final isFav = _favoritesController.isFavorited(_recipe!.id); + if (mounted) setState(() => _isFavorite = isFav); + } catch (e) { + debugPrint('检查收藏状态失败: $e'); + } } } @@ -83,9 +123,45 @@ class _RecipeDetailPageState extends State { void _toggleFavorite() { if (_recipe != null) { - final feedItem = FeedItemModel.fromRecipe(_recipe!); - _favoritesController.toggleFavorite(feedItem); - setState(() => _isFavorite = !_isFavorite); + try { + final feedItem = FeedItemModel.fromRecipe(_recipe!); + _favoritesController.toggleFavorite(feedItem); + setState(() => _isFavorite = !_isFavorite); + } catch (e) { + debugPrint('切换收藏失败:$e'); + } + } + } + + Future _handleRefresh() async { + setState(() { + _isLoading = true; + _loadTimeout = false; + }); + + try { + final recipeId = int.tryParse(widget.recipeId) ?? 0; + final recipe = await _recipeRepository.fetchFull( + recipeId, + viewnums: false, + ); + if (mounted) { + setState(() { + _recipe = recipe; + _isLoading = false; + _likeCount = recipe.statistics?.likes ?? 0; + _viewCount = recipe.statistics?.views ?? 0; + }); + _checkFavorite(); + ToastService.show(message: '✅ 刷新成功'); + } + } catch (e) { + if (mounted) { + setState(() { + _isLoading = false; + }); + ToastService.show(message: '❌ 刷新失败:$e'); + } } } @@ -94,15 +170,7 @@ class _RecipeDetailPageState extends State { final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; if (_isLoading) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: const Text('菜谱详情'), - backgroundColor: isDark - ? DarkDesignTokens.background - : DesignTokens.background, - ), - child: const Center(child: CupertinoActivityIndicator()), - ); + return _buildSkeletonView(isDark); } if (_recipe == null) { @@ -123,9 +191,9 @@ class _RecipeDetailPageState extends State { color: DesignTokens.text3, ), const SizedBox(height: 16), - const Text( + Text( '菜谱不存在', - style: TextStyle(fontSize: 16, color: DesignTokens.text2), + style: createTextStyle(color: DesignTokens.text2, fontSize: 16), ), const SizedBox(height: 16), CupertinoButton( @@ -157,36 +225,196 @@ class _RecipeDetailPageState extends State { ? DarkDesignTokens.background : DesignTokens.background, ), + child: RefreshIndicator( + onRefresh: _handleRefresh, + color: DesignTokens.primary, + child: ListView( + padding: const EdgeInsets.only(bottom: DesignTokens.space5), + physics: const AlwaysScrollableScrollPhysics(), + children: [ + _buildCoverImage(), + _buildTitleSection(isDark), + _buildStatisticsBar(isDark), + _buildAuthorCard(isDark), + _buildPicIdCard(isDark), + _buildCategoryBreadcrumb(isDark), + _buildMetaInfoCard(isDark), + _buildIndicesCard(isDark), + _buildTagsSection(isDark), + _buildCategorizedIngredients(isDark), + _buildAllergenWarning(isDark), + _buildApiAllergens(isDark), + _buildStepsSection(isDark), + _buildNutritionSummary(isDark), + _buildNutritionDetail(isDark), + _buildIngredientDetails(isDark), + _buildTimeInfo(isDark), + _buildActions(), + ], + ), + ), + ); + } + + Widget _buildSkeletonView(bool isDark) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('菜谱详情'), + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + ), child: ListView( padding: const EdgeInsets.only(bottom: DesignTokens.space5), children: [ - _buildCoverImage(), - _buildTitleSection(isDark), - _buildStatisticsBar(isDark), - _buildAuthorCard(isDark), - _buildCategoryBreadcrumb(isDark), - _buildMetaInfoCard(isDark), - _buildIndicesCard(isDark), - _buildTagsSection(isDark), - _buildCategorizedIngredients(isDark), - _buildAllergenWarning(isDark), - _buildApiAllergens(isDark), - _buildStepsSection(isDark), - _buildNutritionSummary(isDark), - _buildNutritionDetail(isDark), - _buildIngredientDetails(isDark), - _buildTimeInfo(isDark), - _buildActions(), + SkeletonLoader( + width: double.infinity, + height: 250, + borderRadius: BorderRadius.zero, + isDark: isDark, + ), + Padding( + padding: const EdgeInsets.all(DesignTokens.space4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SkeletonLoader( + width: 200, + height: 28, + borderRadius: DesignTokens.borderRadiusSm, + isDark: isDark, + ), + const SizedBox(height: DesignTokens.space3), + SkeletonLoader( + width: double.infinity, + height: 16, + borderRadius: DesignTokens.borderRadiusSm, + isDark: isDark, + ), + const SizedBox(height: DesignTokens.space2), + SkeletonLoader( + width: 150, + height: 14, + borderRadius: DesignTokens.borderRadiusSm, + isDark: isDark, + ), + const SizedBox(height: DesignTokens.space4), + Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.card : DesignTokens.card) + .withValues(alpha: 0.5), + borderRadius: DesignTokens.borderRadiusMd, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildSkeletonStatItem(isDark), + _buildSkeletonStatItem(isDark), + _buildSkeletonStatItem(isDark), + _buildSkeletonStatItem(isDark), + ], + ), + ), + const SizedBox(height: DesignTokens.space3), + SkeletonLoader( + width: double.infinity, + height: 80, + borderRadius: DesignTokens.borderRadiusMd, + isDark: isDark, + ), + const SizedBox(height: DesignTokens.space3), + SkeletonLoader( + width: double.infinity, + height: 120, + borderRadius: DesignTokens.borderRadiusMd, + isDark: isDark, + ), + if (_loadTimeout) ...[ + const SizedBox(height: DesignTokens.space3), + Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: DesignTokens.orange.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusMd, + border: Border.all( + color: DesignTokens.orange.withValues(alpha: 0.3), + ), + ), + child: Row( + children: [ + Icon( + CupertinoIcons.clock, + color: DesignTokens.orange, + size: 20, + ), + const SizedBox(width: DesignTokens.space2), + Expanded( + child: Text( + '加载时间较长,请检查网络连接...', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.orange, + ), + ), + ), + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: _loadRecipeWithTimeout, + child: const Text( + '重试', + style: TextStyle( + color: DesignTokens.primary, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ), + ], + ], + ), + ), ], ), ); } + Widget _buildSkeletonStatItem(bool isDark) { + return Column( + children: [ + SkeletonLoader( + width: 40, + height: 40, + borderRadius: BorderRadius.circular(20), + isDark: isDark, + ), + const SizedBox(height: 6), + SkeletonLoader( + width: 36, + height: 16, + borderRadius: DesignTokens.borderRadiusSm, + isDark: isDark, + ), + const SizedBox(height: 4), + SkeletonLoader( + width: 30, + height: 12, + borderRadius: DesignTokens.borderRadiusSm, + isDark: isDark, + ), + ], + ); + } + Widget _buildCoverImage() { return Stack( children: [ RecipeImage.full( recipeId: _recipe!.id, + picId: _recipe!.picId, coverUrl: _recipe!.cover, height: 250, width: double.infinity, @@ -438,6 +666,252 @@ class _RecipeDetailPageState extends State { ); } + void _showCopyToast(String message) { + try { + if (mounted) { + ToastService.show(message: message); + } + } catch (e) { + debugPrint('Toast显示失败: $e'); + } + } + + Widget _buildPicIdCard(bool isDark) { + final picId = _recipe?.picId; + final coverUrl = _recipe?.cover; + + debugPrint( + '🔍 PicId调试: picId=$picId, coverUrl=$coverUrl, recipeId=${_recipe?.id}', + ); + + if (picId == null && (coverUrl == null || coverUrl.isEmpty)) { + return const SizedBox(); + } + + final hasValidPicId = picId != null && picId > 0; + final picIdStr = hasValidPicId ? picId.toString() : '无'; + + String mainImageUrl = '暂无图片链接'; + if (hasValidPicId) { + mainImageUrl = 'http://eat.wktyl.com/api/assets/pic/${picId}a.jpg'; + } else if (coverUrl?.isNotEmpty == true) { + mainImageUrl = coverUrl!; + } + + debugPrint('🔍 PicId显示: hasValidPicId=$hasValidPicId, picIdStr=$picIdStr'); + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space2, + ), + child: Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.card.withValues(alpha: 0.5) + : DesignTokens.card.withValues(alpha: 0.8), + borderRadius: DesignTokens.borderRadiusMd, + border: Border.all( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + CupertinoIcons.photo, + size: 16, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + ), + const SizedBox(width: DesignTokens.space1), + Text( + '图片信息', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + GestureDetector( + onTap: () { + if (hasValidPicId) { + try { + Clipboard.setData( + ClipboardData(text: picId.toString()), + ); + _showCopyToast('Picid 已复制: $picId ✅'); + } catch (e) { + debugPrint('复制失败: $e'); + _showCopyToast('复制失败,请重试'); + } + } else { + _showCopyToast('此菜谱无有效 Picid'); + } + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: + (isDark + ? DarkDesignTokens.primary + : DesignTokens.primary) + .withValues(alpha: 0.15), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + CupertinoIcons.doc_on_doc, + size: 12, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + ), + const SizedBox(width: 4), + Text( + '复制Picid', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + ), + ), + ], + ), + ), + ), + const SizedBox(width: DesignTokens.space2), + GestureDetector( + onTap: () { + if (mainImageUrl != '暂无图片链接') { + try { + Clipboard.setData(ClipboardData(text: mainImageUrl)); + _showCopyToast('图片URL已复制 ✅'); + } catch (e) { + debugPrint('复制失败: $e'); + _showCopyToast('复制失败,请重试'); + } + } else { + _showCopyToast('此菜谱无图片链接'); + } + }, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: + (isDark + ? DarkDesignTokens.primary + : DesignTokens.primary) + .withValues(alpha: 0.15), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + CupertinoIcons.link, + size: 12, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + ), + const SizedBox(width: 4), + Text( + '复制URL', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + ), + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + Container( + padding: const EdgeInsets.all(DesignTokens.space2), + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + 'Picid: ', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + SelectableText( + picIdStr, + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + fontFamily: 'monospace', + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + Text( + '图片链接:', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + const SizedBox(height: DesignTokens.space1), + SelectableText( + mainImageUrl, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + fontFamily: 'monospace', + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + Widget _buildCategoryBreadcrumb(bool isDark) { final hierarchy = _recipe?.categoryHierarchy ?? []; if (hierarchy.isEmpty && _recipe?.categoryName == null) { @@ -1348,8 +1822,12 @@ class _RecipeDetailPageState extends State { ), const SizedBox(height: DesignTokens.space2), ...ingredientsWithDetail.map( - (ing) => - _buildIngredientDetailCard(ing.name, ing.detail!, isDark), + (ing) => _buildIngredientDetailCard( + ing.id, + ing.name, + ing.detail!, + isDark, + ), ), ], ), @@ -1358,6 +1836,7 @@ class _RecipeDetailPageState extends State { } Widget _buildIngredientDetailCard( + int? id, String name, IngredientDetail detail, bool isDark, @@ -1374,117 +1853,64 @@ class _RecipeDetailPageState extends State { .withValues(alpha: 0.08), ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - Row( - children: [ - Text( - name, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - ), - if (detail.hasAlias) ...[ - const SizedBox(width: 6), - Text( - '(${detail.alias.join('、')})', - style: TextStyle( - fontSize: DesignTokens.fontXs, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, - ), - ), - ], - ], - ), - if (detail.hasIntroduction) ...[ - const SizedBox(height: DesignTokens.space2), - _buildDetailRow('📝 介绍', detail.introduction!, isDark), - ], - if (detail.hasUsageTip) ...[ - const SizedBox(height: DesignTokens.space2), - _buildDetailRow('💡 用法提示', detail.usageTip.join('\n'), isDark), - ], - if (detail.hasNutrition) ...[ - const SizedBox(height: DesignTokens.space2), - _buildDetailRow('🧬 营养', detail.nutrition!, isDark), - ], - if (detail.hasGuidance) ...[ - const SizedBox(height: DesignTokens.space2), - _buildDetailRow('📋 指导', detail.guidance!, isDark), - ], - if (detail.hasEffect) ...[ - const SizedBox(height: DesignTokens.space2), - _buildDetailRow('💊 功效', detail.effect!, isDark), - ], - if (detail.hasAllergen) ...[ - const SizedBox(height: DesignTokens.space2), - Wrap( - spacing: 4, - runSpacing: 4, - children: detail.allergen.map((a) { - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: DesignTokens.orange.withValues(alpha: 0.1), - borderRadius: DesignTokens.borderRadiusSm, - ), - child: Text( - '⚠️ $a', + Expanded( + child: GestureDetector( + onTap: () { + Get.toNamed( + '/tools/ingredient', + arguments: {'name': name, 'id': id, 'detail': detail}, + ); + }, + child: Row( + children: [ + Text( + name, style: TextStyle( - fontSize: DesignTokens.fontXs, - color: DesignTokens.orange, + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, + decoration: TextDecoration.underline, ), ), - ); - }).toList(), + if (detail.hasAlias) ...[ + const SizedBox(width: 6), + Text( + '(${detail.alias.join('、')})', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ], + ), ), - ], + ), + Icon( + CupertinoIcons.chevron_right, + size: 16, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), ], ), ), ); } - Widget _buildDetailRow(String label, String content, bool isDark) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: TextStyle( - fontSize: DesignTokens.fontXs, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, - ), - ), - const SizedBox(height: 2), - Text( - content, - style: TextStyle( - fontSize: DesignTokens.fontSm, - color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, - height: 1.5, - ), - ), - ], - ); - } - Widget _buildTimeInfo(bool isDark) { final hasCreated = _recipe?.createdAt != null; final hasUpdated = _recipe?.updatedAt != null; final hasCategory = _recipe?.categoryName != null; final hasCode = _recipe?.hasCode ?? false; + final hasStatus = _recipe?.status != null; - if (!hasCreated && !hasUpdated && !hasCategory && !hasCode) { + if (!hasCreated && !hasUpdated && !hasCategory && !hasCode && !hasStatus) { return const SizedBox(); } @@ -1506,6 +1932,7 @@ class _RecipeDetailPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (hasStatus) _buildStatusRow('📊 状态', _recipe!.status!, isDark), if (hasCategory) _buildTimeRow('📂 分类', _recipe!.categoryName!, isDark), if (hasCode) _buildTimeRow('🔢 编码', _recipe!.code!, isDark), @@ -1547,6 +1974,64 @@ class _RecipeDetailPageState extends State { ); } + Widget _buildStatusRow(String label, int status, bool isDark) { + String statusText; + Color statusColor; + + switch (status) { + case 0: + statusText = '✅ 正常'; + statusColor = CupertinoColors.systemGreen; + break; + case 1: + statusText = '⏸️ 草稿'; + statusColor = CupertinoColors.systemOrange; + break; + case 2: + statusText = '🚫 禁用'; + statusColor = CupertinoColors.systemRed; + break; + default: + statusText = '❓ 未知($status)'; + statusColor = CupertinoColors.systemGrey; + } + + return Padding( + padding: const EdgeInsets.only(bottom: DesignTokens.space1), + child: Row( + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + fontWeight: FontWeight.w500, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + border: Border.all(color: statusColor.withValues(alpha: 0.3)), + ), + child: Text( + statusText, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: statusColor, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + ); + } + Widget _buildActions() { final isLiked = _actionController.isLiked(_recipe!.id); final isRecommended = _actionController.isRecommended(_recipe!.id); diff --git a/lib/src/pages/home/search_page.dart b/lib/src/pages/home/search_page.dart index eea776c..2355294 100644 --- a/lib/src/pages/home/search_page.dart +++ b/lib/src/pages/home/search_page.dart @@ -106,10 +106,12 @@ class _SearchPageState extends State { focusNode: _focusNode, placeholder: '搜索菜谱、食材...', placeholderStyle: TextStyle( + inherit: false, fontSize: 15, color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, ), style: TextStyle( + inherit: false, fontSize: 15, color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, ), @@ -184,12 +186,12 @@ class _SearchPageState extends State { children: [ Text( '🕐 搜索历史', - style: TextStyle( - fontSize: DesignTokens.fontLg, - fontWeight: FontWeight.w600, + style: createTextStyle( color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, ), ), CupertinoButton( @@ -201,11 +203,11 @@ class _SearchPageState extends State { onPressed: () => _searchController.clearSearchHistory(), child: Text( '清空', - style: TextStyle( - fontSize: DesignTokens.fontSm, + style: createTextStyle( color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + fontSize: DesignTokens.fontSm, ), ), ), @@ -238,11 +240,11 @@ class _SearchPageState extends State { ), child: Text( history, - style: TextStyle( - fontSize: DesignTokens.fontSm, + style: createTextStyle( color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + fontSize: DesignTokens.fontSm, ), ), ), @@ -260,10 +262,10 @@ class _SearchPageState extends State { children: [ Text( '🔥 热门搜索', - style: TextStyle( + style: createTextStyle( + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, fontSize: DesignTokens.fontLg, fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, ), ), const SizedBox(height: DesignTokens.space3), @@ -309,11 +311,11 @@ class _SearchPageState extends State { children: [ Text( keyword, - style: TextStyle( - fontSize: DesignTokens.fontSm, + style: createTextStyle( color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + fontSize: DesignTokens.fontSm, fontWeight: FontWeight.w500, ), ), @@ -497,6 +499,7 @@ class _SearchPageState extends State { borderRadius: DesignTokens.borderRadiusSm, child: RecipeImage( recipeId: recipe.id, + picId: recipe.picId, coverUrl: recipe.cover, width: 48, height: 48, @@ -598,6 +601,7 @@ class _SearchPageState extends State { final category = recipe.categoryName; final cover = recipe.cover; final recipeId = recipe.id; + final picId = recipe.picId; return GestureDetector( behavior: HitTestBehavior.opaque, @@ -642,6 +646,7 @@ class _SearchPageState extends State { borderRadius: BorderRadius.circular(DesignTokens.radiusSm), child: RecipeImage( recipeId: recipeId ?? 0, + picId: picId, coverUrl: cover, width: 90, height: 90, diff --git a/lib/src/pages/profile/bedtime_reminder_page.dart b/lib/src/pages/profile/bedtime_reminder_page.dart new file mode 100644 index 0000000..af41530 --- /dev/null +++ b/lib/src/pages/profile/bedtime_reminder_page.dart @@ -0,0 +1,595 @@ +/* + * 文件: bedtime_reminder_page.dart + * 名称: 就寝提醒页面 + * 作用: 根据晚餐时间推荐就寝时间,睡前不宜进食提醒 + * 创建: 2026-04-11 + * 更新: 2026-04-11 初始实现 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/controllers/bedtime_reminder_controller.dart'; + +class BedtimeReminderPage extends StatelessWidget { + const BedtimeReminderPage({super.key}); + + @override + Widget build(BuildContext context) { + final controller = Get.find(); + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: Text( + '🌙 就寝提醒', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + backgroundColor: isDark + ? DarkDesignTokens.background.withValues(alpha: 0.9) + : DesignTokens.background.withValues(alpha: 0.9), + border: null, + ), + child: SafeArea( + child: Obx(() => SingleChildScrollView( + padding: const EdgeInsets.all(DesignTokens.space4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDinnerTimeCard(controller, isDark), + const SizedBox(height: DesignTokens.space4), + _buildBedtimeRecommendationCard(controller, isDark), + const SizedBox(height: DesignTokens.space4), + _buildReminderSettingsCard(controller, isDark), + const SizedBox(height: DesignTokens.space4), + _buildBeforeSleepEatingCard(controller, isDark), + const SizedBox(height: DesignTokens.space4), + _buildHealthTipsCard(isDark), + ], + ), + )), + ), + ); + } + + Widget _buildDinnerTimeCard(BedtimeReminderController controller, bool isDark) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusLg), + border: Border.all( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Text('🍽️', style: TextStyle(fontSize: 24)), + ), + const SizedBox(width: DesignTokens.space3), + Text( + '晚餐时间', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space4), + Row( + children: [ + Expanded( + child: CupertinoButton( + padding: const EdgeInsets.all(DesignTokens.space3), + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + onPressed: () => _showTimePicker(controller, isDark), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + controller.dinnerTimeDisplay, + style: TextStyle( + fontSize: DesignTokens.fontXxl, + fontWeight: FontWeight.bold, + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + ), + ), + const SizedBox(width: DesignTokens.space2), + Icon( + CupertinoIcons.clock, + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + ), + ], + ), + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + Text( + '设置您的晚餐时间,系统将根据此时间推荐就寝时间', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ); + } + + Widget _buildBedtimeRecommendationCard( + BedtimeReminderController controller, + bool isDark, + ) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.08), + borderRadius: BorderRadius.circular(DesignTokens.radiusLg), + border: Border.all( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.15), + borderRadius: BorderRadius.circular(8), + ), + child: const Text('🌙', style: TextStyle(fontSize: 24)), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '推荐就寝时间', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(height: 4), + Text( + controller.bedtimeRecommendation, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space4), + Container( + width: double.infinity, + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + controller.recommendedBedtimeDisplay, + style: TextStyle( + fontSize: 48, + fontWeight: FontWeight.bold, + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildReminderSettingsCard( + BedtimeReminderController controller, + bool isDark, + ) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusLg), + border: Border.all( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Text('🔔', style: TextStyle(fontSize: 24)), + ), + const SizedBox(width: DesignTokens.space3), + Text( + '提醒设置', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space4), + _buildSwitchRow( + '启用就寝提醒', + controller.reminderEnabled.value, + (value) => controller.toggleReminder(value), + isDark, + ), + const SizedBox(height: DesignTokens.space3), + _buildSwitchRow( + '提前提醒', + controller.advanceReminderEnabled.value, + (value) => controller.toggleAdvanceReminder(value), + isDark, + ), + if (controller.advanceReminderEnabled.value) ...[ + const SizedBox(height: DesignTokens.space3), + _buildSliderRow( + '提前 ${controller.advanceReminderMinutes.value} 分钟', + controller.advanceReminderMinutes.value.toDouble(), + 15, + 60, + (value) => controller.updateAdvanceReminderMinutes(value.toInt()), + isDark, + ), + ], + ], + ), + ); + } + + Widget _buildBeforeSleepEatingCard( + BedtimeReminderController controller, + bool isDark, + ) { + final shouldShowWarning = controller.shouldShowBeforeSleepEatingWarning(); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: shouldShowWarning + ? DesignTokens.red.withValues(alpha: 0.08) + : (isDark ? DarkDesignTokens.card : DesignTokens.card), + borderRadius: BorderRadius.circular(DesignTokens.radiusLg), + border: Border.all( + color: shouldShowWarning + ? DesignTokens.red.withValues(alpha: 0.3) + : (isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15)), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: shouldShowWarning + ? DesignTokens.red.withValues(alpha: 0.15) + : (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + shouldShowWarning ? '⚠️' : '🍎', + style: const TextStyle(fontSize: 24), + ), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '睡前进食提醒', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + if (shouldShowWarning) ...[ + const SizedBox(height: 4), + Text( + '当前距离晚餐已超过阈值', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.red, + fontWeight: FontWeight.w500, + ), + ), + ], + ], + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space4), + _buildSwitchRow( + '启用睡前进食提醒', + controller.beforeSleepEatingReminder.value, + (value) => controller.toggleBeforeSleepEatingReminder(value), + isDark, + ), + if (controller.beforeSleepEatingReminder.value) ...[ + const SizedBox(height: DesignTokens.space3), + _buildSliderRow( + '睡前 ${controller.beforeSleepEatingThreshold.value} 小时内不宜进食', + controller.beforeSleepEatingThreshold.value.toDouble(), + 1, + 4, + (value) => controller.updateBeforeSleepEatingThreshold(value.toInt()), + isDark, + ), + ], + const SizedBox(height: DesignTokens.space2), + Text( + controller.beforeSleepEatingWarning, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ); + } + + Widget _buildHealthTipsCard(bool isDark) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusLg), + border: Border.all( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Text('💡', style: TextStyle(fontSize: 24)), + ), + const SizedBox(width: DesignTokens.space3), + Text( + '健康小贴士', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space4), + _buildTipItem('晚餐后保持直立,避免立即躺下', isDark), + const SizedBox(height: DesignTokens.space3), + _buildTipItem('睡前2-3小时避免大量进食', isDark), + const SizedBox(height: DesignTokens.space3), + _buildTipItem('保持规律作息,有助于消化系统健康', isDark), + const SizedBox(height: DesignTokens.space3), + _buildTipItem('晚餐宜清淡,避免油腻和辛辣食物', isDark), + ], + ), + ); + } + + Widget _buildTipItem(String text, bool isDark) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.only(top: 2), + width: 6, + height: 6, + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: DesignTokens.space2), + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + height: 1.5, + ), + ), + ), + ], + ); + } + + Widget _buildSwitchRow( + String label, + bool value, + Function(bool) onChanged, + bool isDark, + ) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + CupertinoSwitch( + value: value, + onChanged: onChanged, + activeTrackColor: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + ), + ], + ); + } + + Widget _buildSliderRow( + String label, + double value, + double min, + double max, + Function(double) onChanged, + bool isDark, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + CupertinoSlider( + value: value, + min: min, + max: max, + divisions: (max - min).toInt(), + onChanged: onChanged, + ), + ], + ); + } + + void _showTimePicker(BedtimeReminderController controller, bool isDark) { + showCupertinoModalPopup( + context: Get.context!, + builder: (BuildContext context) { + return Container( + height: 300, + padding: const EdgeInsets.only(top: 6), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: isDark ? DarkDesignTokens.card : CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: Column( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CupertinoButton( + child: const Text('取消'), + onPressed: () => Navigator.of(context).pop(), + ), + CupertinoButton( + child: const Text('确定'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + Expanded( + child: CupertinoTimerPicker( + mode: CupertinoTimerPickerMode.hm, + initialTimerDuration: Duration( + hours: controller.dinnerHour.value, + minutes: controller.dinnerMinute.value, + ), + onTimerDurationChanged: (Duration changedTimer) { + controller.updateDinnerTime( + changedTimer.inHours, + changedTimer.inMinutes.remainder(60), + ); + }, + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/pages/profile/nutrition/nutrition_center_page.dart b/lib/src/pages/profile/nutrition/nutrition_center_page.dart index 61d0028..ab7f5d0 100644 --- a/lib/src/pages/profile/nutrition/nutrition_center_page.dart +++ b/lib/src/pages/profile/nutrition/nutrition_center_page.dart @@ -119,14 +119,8 @@ class _NutritionCenterPageState extends State { const SizedBox(width: DesignTokens.space2), GestureDetector( onTap: () { - debugPrint('NutritionCenterPage: tapping today button'); - debugPrint('NutritionCenterPage: _ctrl=${_ctrl}'); - debugPrint('NutritionCenterPage: calling selectToday()'); try { _ctrl?.selectToday(); - debugPrint( - 'NutritionCenterPage: selectToday called successfully', - ); ToastService.show(message: '已跳转到今天 📅'); } catch (e, stackTrace) { debugPrint('selectToday error: $e'); diff --git a/lib/src/pages/tools/cooking_note_page.dart b/lib/src/pages/tools/cooking_note_page.dart index cf057c0..a2d02f0 100644 --- a/lib/src/pages/tools/cooking_note_page.dart +++ b/lib/src/pages/tools/cooking_note_page.dart @@ -28,7 +28,13 @@ class CookingNotePage extends StatefulWidget { } class _CookingNotePageState extends State { - final CookingNoteController _controller = CookingNoteController.to; + late final CookingNoteController _controller; + + @override + void initState() { + super.initState(); + _controller = Get.find(); + } @override Widget build(BuildContext context) { @@ -41,10 +47,10 @@ class _CookingNotePageState extends State { navigationBar: CupertinoNavigationBar( middle: Text( '📝 烹饪笔记', - style: TextStyle( + style: createTextStyle( + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, fontSize: DesignTokens.fontMd, fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, ), ), trailing: CupertinoButton( @@ -61,7 +67,12 @@ class _CookingNotePageState extends State { : DesignTokens.background.withValues(alpha: 0.9), border: null, ), - child: SafeArea(top: false, child: Obx(() => _buildBody(isDark))), + child: SafeArea( + top: false, + child: Obx(() { + return _buildBody(isDark); + }), + ), ); } @@ -80,17 +91,17 @@ class _CookingNotePageState extends State { const SizedBox(height: DesignTokens.space4), Text( '还没有笔记', - style: TextStyle( - fontSize: DesignTokens.fontLg, + style: createTextStyle( color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + fontSize: DesignTokens.fontLg, ), ), const SizedBox(height: DesignTokens.space2), Text( '点击右上角 + 记录烹饪心得', - style: TextStyle( - fontSize: DesignTokens.fontSm, + style: createTextStyle( color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + fontSize: DesignTokens.fontSm, ), ), if (widget.recipeTitle.isNotEmpty) ...[ @@ -101,9 +112,9 @@ class _CookingNotePageState extends State { ), child: Text( '📖 ${widget.recipeTitle}', - style: TextStyle( - fontSize: DesignTokens.fontMd, + style: createTextStyle( color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + fontSize: DesignTokens.fontMd, ), textAlign: TextAlign.center, maxLines: 2, @@ -291,7 +302,7 @@ class _CookingNotePageState extends State { } void _showAddDialog(bool isDark) { - final controller = TextEditingController(); + final textController = TextEditingController(); showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( @@ -301,7 +312,7 @@ class _CookingNotePageState extends State { child: SizedBox( height: 120, child: CupertinoTextField( - controller: controller, + controller: textController, placeholder: '记录你的烹饪心得...', maxLines: 5, autofocus: true, @@ -320,11 +331,11 @@ class _CookingNotePageState extends State { CupertinoDialogAction( isDefaultAction: true, onPressed: () async { - if (controller.text.trim().isNotEmpty) { + if (textController.text.trim().isNotEmpty) { final note = CookingNoteModel( id: DateTime.now().millisecondsSinceEpoch.toString(), recipeId: widget.recipeId, - content: controller.text.trim(), + content: textController.text.trim(), createdAt: DateTime.now().toIso8601String(), ); await _controller.addNote(note); @@ -341,7 +352,7 @@ class _CookingNotePageState extends State { } void _showEditDialog(CookingNoteModel note, bool isDark) { - final controller = TextEditingController(text: note.content); + final textController = TextEditingController(text: note.content); showCupertinoDialog( context: context, builder: (ctx) => CupertinoAlertDialog( @@ -351,7 +362,7 @@ class _CookingNotePageState extends State { child: SizedBox( height: 120, child: CupertinoTextField( - controller: controller, + controller: textController, maxLines: 5, autofocus: true, style: TextStyle( @@ -369,8 +380,10 @@ class _CookingNotePageState extends State { CupertinoDialogAction( isDefaultAction: true, onPressed: () async { - if (controller.text.trim().isNotEmpty) { - final updated = note.copyWith(content: controller.text.trim()); + if (textController.text.trim().isNotEmpty) { + final updated = note.copyWith( + content: textController.text.trim(), + ); await _controller.updateNote(updated); ToastService.show(message: '笔记已更新 ✅'); if (mounted) setState(() {}); diff --git a/lib/src/pages/tools/eating_times_page.dart b/lib/src/pages/tools/eating_times_page.dart index d09a8b7..96d3eca 100644 --- a/lib/src/pages/tools/eating_times_page.dart +++ b/lib/src/pages/tools/eating_times_page.dart @@ -399,6 +399,7 @@ class _EatingTimesPageState extends State { ) .timeout(const Duration(seconds: 10)); + if (!mounted) return; Navigator.of(context).pop(); if (result.items.isNotEmpty) { @@ -412,8 +413,8 @@ class _EatingTimesPageState extends State { _showToast('暂无"${item.name}"相关菜谱'); } } catch (e) { + if (!mounted) return; Navigator.of(context).pop(); - debugPrint('Browse by time error: $e'); _showToast('加载失败,请重试'); } } @@ -488,6 +489,7 @@ class EatingTimeRecipesPage extends StatelessWidget { borderRadius: DesignTokens.borderRadiusSm, child: RecipeImage( recipeId: recipe.id, + picId: recipe.picId, coverUrl: recipe.cover, width: 64, height: 64, diff --git a/lib/src/pages/tools/ingredient_detail_page.dart b/lib/src/pages/tools/ingredient_detail_page.dart index 47e4719..60193b8 100644 --- a/lib/src/pages/tools/ingredient_detail_page.dart +++ b/lib/src/pages/tools/ingredient_detail_page.dart @@ -3,12 +3,14 @@ * 名称: 食材详情查询页面 * 作用: 查询食材营养信息与选购指南 * 创建: 2026-04-10 - * 更新: 2026-04-11 接入营养数据库,展示真实营养数据+选购指南+营养素进度条 + * 更新: 2026-04-11 从菜谱详情页接收食材详情数据,直接展示introduction/nutrition/guidance/effect/allergen */ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/models/recipe/ingredient_model.dart'; +import 'package:mom_kitchen/src/models/recipe/recipe_model.dart'; import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; import 'package:mom_kitchen/src/services/data/ingredient_nutrition_db.dart'; @@ -26,7 +28,10 @@ class _IngredientDetailPageState extends State { List> _ingredients = []; List> _filteredIngredients = []; Map? _selectedIngredient; + IngredientDetail? _passedDetail; + IngredientModel? _ingredientDetail; bool _isLoading = false; + bool _isLoadingDetail = false; String _searchQuery = ''; @override @@ -34,9 +39,87 @@ class _IngredientDetailPageState extends State { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _loadIngredients(); + _loadIngredientFromArgs(); }); } + void _loadIngredientFromArgs() { + final args = Get.arguments; + if (args == null) return; + + String? ingredientName; + int? ingredientId; + IngredientDetail? passedDetail; + + if (args is String) { + ingredientName = args; + } else if (args is Map) { + ingredientName = args['name'] as String?; + ingredientId = args['id'] as int?; + final detailMap = args['detail']; + if (detailMap is IngredientDetail) { + passedDetail = detailMap; + } else if (detailMap is Map) { + try { + passedDetail = IngredientDetail.fromJson(detailMap); + } catch (e) { + debugPrint('Failed to parse IngredientDetail: $e'); + } + } + } + + setState(() { + _passedDetail = passedDetail; + }); + + if (ingredientName != null && ingredientName.isNotEmpty) { + _loadIngredientByName(ingredientName, ingredientId); + } + } + + void _loadIngredientByName(String name, int? id) { + final ingredient = _ingredients.firstWhere( + (ing) => ing['name'] == name, + orElse: () { + return { + 'id': id ?? 0, + 'name': name, + 'count': 0, + 'category': _getIngredientCategory(name), + }; + }, + ); + + final resolvedId = ingredient['id'] as int? ?? id; + + setState(() { + _selectedIngredient = ingredient; + }); + + if (_passedDetail == null && resolvedId != null && resolvedId > 0) { + _loadIngredientDetailApi(resolvedId); + } + } + + Future _loadIngredientDetailApi(int id) async { + setState(() => _isLoadingDetail = true); + + try { + final detail = await _recipeRepository.fetchIngredientDetail(id); + if (mounted) { + setState(() { + _ingredientDetail = detail; + _isLoadingDetail = false; + }); + } + } catch (e) { + debugPrint('Load ingredient detail error: $e'); + if (mounted) { + setState(() => _isLoadingDetail = false); + } + } + } + Future _loadIngredients() async { setState(() => _isLoading = true); @@ -120,7 +203,10 @@ class _IngredientDetailPageState extends State { name.contains('橙') || name.contains('橘') || name.contains('柠') || - name.contains('瓜') && !name.contains('冬') && !name.contains('南') && !name.contains('黄')) { + name.contains('瓜') && + !name.contains('冬') && + !name.contains('南') && + !name.contains('黄')) { return '水果'; } else if (name.contains('坚果') || name.contains('花生') || @@ -153,8 +239,9 @@ class _IngredientDetailPageState extends State { final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; return CupertinoPageScaffold( - backgroundColor: - isDark ? DarkDesignTokens.background : DesignTokens.background, + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, navigationBar: CupertinoNavigationBar( middle: Text( '🥕 食材详情', @@ -302,8 +389,9 @@ class _IngredientDetailPageState extends State { width: 48, height: 48, decoration: BoxDecoration( - color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) - .withValues(alpha: 0.1), + color: + (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), borderRadius: DesignTokens.borderRadiusMd, ), child: Center( @@ -398,6 +486,33 @@ class _IngredientDetailPageState extends State { return ListView( padding: const EdgeInsets.all(DesignTokens.space4), children: [ + if (_isLoadingDetail) + Container( + padding: const EdgeInsets.all(DesignTokens.space3), + margin: const EdgeInsets.only(bottom: DesignTokens.space3), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusMd, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const CupertinoActivityIndicator(radius: 10), + const SizedBox(width: DesignTokens.space2), + Text( + '加载食材详情...', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ), + if (_passedDetail != null) + _buildPassedDetailCard(_passedDetail!, isDark), + if (_ingredientDetail != null && _passedDetail == null) + _buildApiDetailCard(_ingredientDetail!, isDark), _buildDetailHeader(name, category, nutrition, isDark), const SizedBox(height: DesignTokens.space4), _buildCalorieOverview(nutrition, isDark), @@ -424,6 +539,8 @@ class _IngredientDetailPageState extends State { onPressed: () { setState(() { _selectedIngredient = null; + _ingredientDetail = null; + _passedDetail = null; }); }, child: const Text('返回列表'), @@ -433,6 +550,343 @@ class _IngredientDetailPageState extends State { ); } + Widget _buildPassedDetailCard(IngredientDetail detail, bool isDark) { + final hasContent = + (detail.introduction?.isNotEmpty ?? false) || + (detail.nutrition?.isNotEmpty ?? false) || + (detail.guidance?.isNotEmpty ?? false) || + (detail.effect?.isNotEmpty ?? false) || + detail.allergen.isNotEmpty; + + if (!hasContent) return const SizedBox(); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space3), + margin: const EdgeInsets.only(bottom: DesignTokens.space3), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.08), + borderRadius: DesignTokens.borderRadiusMd, + border: Border.all( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('📖', style: TextStyle(fontSize: 16)), + const SizedBox(width: DesignTokens.space2), + Text( + '食材详解', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + if (detail.introduction?.isNotEmpty ?? false) ...[ + const SizedBox(height: DesignTokens.space2), + Text( + detail.introduction!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + if (detail.nutrition?.isNotEmpty ?? false) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '📊 营养: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.nutrition!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.guidance?.isNotEmpty ?? false) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '🛒 选购: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.guidance!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.effect?.isNotEmpty ?? false) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '💪 功效: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.effect!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.usageTip.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '💡 技巧: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.usageTip.join('; '), + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.allergen.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + children: [ + Text( + '⚠️ 过敏: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: DesignTokens.orange, + ), + ), + Expanded( + child: Text( + detail.allergen.join('、'), + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.orange, + ), + ), + ), + ], + ), + ], + ], + ), + ); + } + + Widget _buildApiDetailCard(IngredientModel detail, bool isDark) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space3), + margin: const EdgeInsets.only(bottom: DesignTokens.space3), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.08), + borderRadius: DesignTokens.borderRadiusMd, + border: Border.all( + color: (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('📖', style: TextStyle(fontSize: 16)), + const SizedBox(width: DesignTokens.space2), + Text( + '食材详情 (API数据)', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + if (detail.introduction != null && + detail.introduction!.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Text( + detail.introduction!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + if (detail.nutrition != null && detail.nutrition!.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '📊 营养: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.nutrition!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.guidance != null && detail.guidance!.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '🛒 选购: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.guidance!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.effect != null && detail.effect!.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '💪 功效: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + Expanded( + child: Text( + detail.effect!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ], + if (detail.allergen.isNotEmpty) ...[ + const SizedBox(height: DesignTokens.space2), + Row( + children: [ + Text( + '⚠️ 过敏: ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: DesignTokens.orange, + ), + ), + Expanded( + child: Text( + detail.allergen.join('、'), + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.orange, + ), + ), + ), + ], + ), + ], + ], + ), + ); + } + Widget _buildDetailHeader( String name, String category, @@ -485,8 +939,9 @@ class _IngredientDetailPageState extends State { style: TextStyle( fontSize: 36, fontWeight: FontWeight.w700, - color: - isDark ? DarkDesignTokens.primary : DesignTokens.primary, + color: isDark + ? DarkDesignTokens.primary + : DesignTokens.primary, ), ), const SizedBox(width: 4), @@ -496,9 +951,7 @@ class _IngredientDetailPageState extends State { 'kcal/${nutrition.unit}', style: TextStyle( fontSize: DesignTokens.fontSm, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, ), ), ), @@ -509,10 +962,7 @@ class _IngredientDetailPageState extends State { ); } - Widget _buildCalorieOverview( - IngredientNutritionData nutrition, - bool isDark, - ) { + Widget _buildCalorieOverview(IngredientNutritionData nutrition, bool isDark) { return Container( padding: const EdgeInsets.all(DesignTokens.space4), decoration: BoxDecoration( @@ -644,10 +1094,7 @@ class _IngredientDetailPageState extends State { ); } - Widget _buildNutritionBars( - IngredientNutritionData nutrition, - bool isDark, - ) { + Widget _buildNutritionBars(IngredientNutritionData nutrition, bool isDark) { final total = nutrition.protein + nutrition.fat + nutrition.carbs; if (total == 0) return const SizedBox(); @@ -734,7 +1181,12 @@ class _IngredientDetailPageState extends State { ); } - Widget _buildLegendItem(String label, String value, Color color, bool isDark) { + Widget _buildLegendItem( + String label, + String value, + Color color, + bool isDark, + ) { return Column( children: [ Row( @@ -809,10 +1261,9 @@ class _IngredientDetailPageState extends State { vertical: DesignTokens.space2, ), decoration: BoxDecoration( - color: (isDark - ? DarkDesignTokens.primary - : DesignTokens.primary) - .withValues(alpha: 0.1), + color: + (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + .withValues(alpha: 0.1), borderRadius: DesignTokens.borderRadiusFull, ), child: Text( @@ -887,8 +1338,8 @@ class _IngredientDetailPageState extends State { color: active ? DesignTokens.green.withValues(alpha: 0.15) : isDark - ? DarkDesignTokens.segmentedBg - : DesignTokens.text3.withValues(alpha: 0.08), + ? DarkDesignTokens.segmentedBg + : DesignTokens.text3.withValues(alpha: 0.08), borderRadius: DesignTokens.borderRadiusFull, ), child: Text( @@ -899,17 +1350,14 @@ class _IngredientDetailPageState extends State { color: active ? DesignTokens.green : isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, + ? DarkDesignTokens.text3 + : DesignTokens.text3, ), ), ); } - Widget _buildPurchaseTipCard( - IngredientNutritionData nutrition, - bool isDark, - ) { + Widget _buildPurchaseTipCard(IngredientNutritionData nutrition, bool isDark) { if (nutrition.purchaseTip.isEmpty) return const SizedBox(); return Container( @@ -969,10 +1417,7 @@ class _IngredientDetailPageState extends State { ); } - Widget _buildStorageTipCard( - IngredientNutritionData nutrition, - bool isDark, - ) { + Widget _buildStorageTipCard(IngredientNutritionData nutrition, bool isDark) { if (nutrition.storageTip.isEmpty) return const SizedBox(); return Container( diff --git a/lib/src/pages/tools/meal_time_recommend_page.dart b/lib/src/pages/tools/meal_time_recommend_page.dart index 09a3dd4..6edd0d1 100644 --- a/lib/src/pages/tools/meal_time_recommend_page.dart +++ b/lib/src/pages/tools/meal_time_recommend_page.dart @@ -10,6 +10,7 @@ import 'package:get/get.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; import 'package:mom_kitchen/src/models/recipe/recipe_model.dart'; import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; +import 'package:mom_kitchen/src/widgets/recipe_image.dart'; import 'package:dio/dio.dart'; class MealTimeRecommendPage extends StatefulWidget { @@ -394,20 +395,15 @@ class _MealTimeRecommendPageState extends State { ), child: Row( children: [ - Container( - width: 60, - height: 60, - decoration: BoxDecoration( - color: - (isDark ? DarkDesignTokens.primary : DesignTokens.primary) - .withValues(alpha: 0.1), - borderRadius: DesignTokens.borderRadiusMd, - ), - child: Center( - child: Text( - recipe.categoryName != null ? '🍽️' : '📖', - style: const TextStyle(fontSize: 28), - ), + ClipRRect( + borderRadius: DesignTokens.borderRadiusMd, + child: RecipeImage( + recipeId: recipe.id, + picId: recipe.picId, + coverUrl: recipe.cover, + width: 60, + height: 60, + mode: RecipeImageMode.thumbnail, ), ), const SizedBox(width: DesignTokens.space3), diff --git a/lib/src/pages/tools/tools_center_page.dart b/lib/src/pages/tools/tools_center_page.dart index f8f909c..26bcc82 100644 --- a/lib/src/pages/tools/tools_center_page.dart +++ b/lib/src/pages/tools/tools_center_page.dart @@ -14,56 +14,39 @@ import 'package:mom_kitchen/src/config/design_tokens.dart'; import 'package:mom_kitchen/src/controllers/tools_controller.dart'; import 'package:mom_kitchen/src/models/tool_item_model.dart'; -class ToolsCenterPage extends StatelessWidget { +class ToolsCenterPage extends StatefulWidget { const ToolsCenterPage({super.key}); + @override + State createState() => _ToolsCenterPageState(); +} + +class _ToolsCenterPageState extends State { + late final ToolsController _controller; + + @override + void initState() { + super.initState(); + try { + _controller = Get.find(); + debugPrint('ToolsCenterPage: Controller found successfully'); + } catch (e) { + debugPrint('ToolsCenterPage: Failed to find controller: $e'); + // 如果找不到控制器,尝试创建一个临时的 + try { + _controller = Get.put(ToolsController(), permanent: true); + debugPrint('ToolsCenterPage: Controller created'); + } catch (e2) { + debugPrint('ToolsCenterPage: Failed to create controller: $e2'); + rethrow; + } + } + } + @override Widget build(BuildContext context) { final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; - - if (!Get.isRegistered()) { - return CupertinoPageScaffold( - navigationBar: CupertinoNavigationBar( - middle: Text( - '🛠️ 工具中心', - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - ), - backgroundColor: isDark - ? DarkDesignTokens.background.withValues(alpha: 0.9) - : DesignTokens.background.withValues(alpha: 0.9), - border: null, - ), - child: SafeArea( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('🛠️', style: TextStyle(fontSize: 48)), - const SizedBox(height: DesignTokens.space3), - Text( - '工具控制器未就绪', - style: TextStyle( - fontSize: DesignTokens.fontMd, - color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, - ), - ), - const SizedBox(height: DesignTokens.space3), - CupertinoButton.filled( - onPressed: () => Get.back(), - child: const Text('返回'), - ), - ], - ), - ), - ), - ); - } - - final controller = Get.find(); + final controller = _controller; return CupertinoPageScaffold( backgroundColor: isDark @@ -250,15 +233,17 @@ class ToolsCenterPage extends StatelessWidget { } return GridView.builder( - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space4, - vertical: DesignTokens.space2, + padding: EdgeInsets.only( + left: DesignTokens.space4, + right: DesignTokens.space4, + top: DesignTokens.space2, + bottom: MediaQuery.of(context).padding.bottom + DesignTokens.space2, ), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: DesignTokens.space3, crossAxisSpacing: DesignTokens.space3, - mainAxisExtent: 140, + childAspectRatio: 0.85, ), itemCount: controller.filteredTools.length, itemBuilder: (context, index) { diff --git a/lib/src/pages/tools/weekly_menu_planner_page.dart b/lib/src/pages/tools/weekly_menu_planner_page.dart new file mode 100644 index 0000000..6935740 --- /dev/null +++ b/lib/src/pages/tools/weekly_menu_planner_page.dart @@ -0,0 +1,642 @@ +/* + * 文件: weekly_menu_planner_page.dart + * 名称: 每周菜单规划页面 + * 作用: 日历视图选择日期,每日三餐分配菜谱,自动生成购物清单 + * 创建: 2026-04-11 + * 更新: 2026-04-11 初始实现 + */ + +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/controllers/weekly_menu_controller.dart'; +import 'package:mom_kitchen/src/models/recipe/recipe_model.dart'; +import 'package:mom_kitchen/src/models/weekly_menu_model.dart'; +import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; + +class WeeklyMenuPlannerPage extends StatefulWidget { + const WeeklyMenuPlannerPage({super.key}); + + @override + State createState() => _WeeklyMenuPlannerPageState(); +} + +class _WeeklyMenuPlannerPageState extends State { + final WeeklyMenuController _controller = Get.find(); + final RecipeRepository _recipeRepository = RecipeRepository(); + final RxList _availableRecipes = [].obs; + + @override + void initState() { + super.initState(); + _loadRecipes(); + } + + Future _loadRecipes() async { + try { + final result = await _recipeRepository.fetchList(); + _availableRecipes.value = result.items; + } catch (e) { + debugPrint('Load recipes error: $e'); + } + } + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: Text( + '📅 每周菜单', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + trailing: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: _showShoppingList, + child: Icon( + CupertinoIcons.cart, + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + ), + ), + backgroundColor: isDark + ? DarkDesignTokens.background.withValues(alpha: 0.9) + : DesignTokens.background.withValues(alpha: 0.9), + border: null, + ), + child: SafeArea( + child: Obx(() => Column( + children: [ + const SizedBox(height: DesignTokens.space3), + _buildWeekHeader(isDark), + const SizedBox(height: DesignTokens.space3), + _buildCalendarView(isDark), + const SizedBox(height: DesignTokens.space4), + Expanded( + child: _buildDayDetailView(isDark), + ), + ], + )), + ), + ); + } + + Widget _buildWeekHeader(bool isDark) { + final menu = _controller.currentMenu.value; + if (menu == null) return const SizedBox(); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space4), + child: Row( + children: [ + Text( + '本周菜单', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + Text( + menu.weekLabel, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + const SizedBox(width: DesignTokens.space2), + Text( + '${_controller.completedMeals}/${_controller.totalPossibleMeals} 餐', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ], + ), + ); + } + + Widget _buildCalendarView(bool isDark) { + final menu = _controller.currentMenu.value; + if (menu == null) return const SizedBox(); + + final weekDays = ['一', '二', '三', '四', '五', '六', '日']; + final startDate = menu.startDate; + + return Container( + height: 80, + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space4), + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: 7, + separatorBuilder: (_, __) => const SizedBox(width: DesignTokens.space2), + itemBuilder: (context, index) { + final date = startDate.add(Duration(days: index)); + final dateKey = _controller.selectedDateKey.value; + final isSelected = dateKey == _formatDateKey(date); + final dayMenu = menu.dailyMenus[_formatDateKey(date)]; + final hasMeals = dayMenu?.hasAnyMeal ?? false; + + return GestureDetector( + onTap: () => _controller.selectDate(_formatDateKey(date)), + child: Container( + width: 56, + decoration: BoxDecoration( + color: isSelected + ? (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + : (isDark ? DarkDesignTokens.card : DesignTokens.card), + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + border: Border.all( + color: isSelected + ? CupertinoColors.transparent + : (isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15)), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + weekDays[index], + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isSelected + ? CupertinoColors.white + : (isDark ? DarkDesignTokens.text2 : DesignTokens.text2), + ), + ), + const SizedBox(height: 4), + Text( + '${date.day}', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.bold, + color: isSelected + ? CupertinoColors.white + : (isDark ? DarkDesignTokens.text1 : DesignTokens.text1), + ), + ), + const SizedBox(height: 4), + if (hasMeals) + Icon( + CupertinoIcons.checkmark_circle_fill, + size: 16, + color: isSelected + ? CupertinoColors.white + : (isDark ? DarkDesignTokens.primary : DesignTokens.primary), + ), + ], + ), + ), + ); + }, + ), + ); + } + + Widget _buildDayDetailView(bool isDark) { + final menu = _controller.currentMenu.value; + if (menu == null) return const SizedBox(); + + final dateKey = _controller.selectedDateKey.value; + final dayMenu = menu.dailyMenus[dateKey]; + if (dayMenu == null) return const SizedBox(); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildMealSlot( + '🌅 早餐', + dayMenu.breakfast, + 'breakfast', + isDark, + ), + const SizedBox(height: DesignTokens.space3), + _buildMealSlot( + '☀️ 午餐', + dayMenu.lunch, + 'lunch', + isDark, + ), + const SizedBox(height: DesignTokens.space3), + _buildMealSlot( + '🌙 晚餐', + dayMenu.dinner, + 'dinner', + isDark, + ), + ], + ), + ); + } + + Widget _buildMealSlot( + String label, + MealItem? meal, + String mealType, + bool isDark, + ) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + border: Border.all( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + label, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + if (meal != null) + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: const Size(32, 32), + onPressed: () => _controller.removeMealFromDay( + _controller.selectedDateKey.value, + mealType, + ), + child: Icon( + CupertinoIcons.delete, + size: 20, + color: DesignTokens.red, + ), + ) + else + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: const Size(32, 32), + onPressed: () => _showRecipePicker(mealType), + child: Icon( + CupertinoIcons.add_circled, + size: 28, + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + ), + ), + ], + ), + if (meal != null) ...[ + const SizedBox(height: DesignTokens.space2), + GestureDetector( + onTap: () { + Get.toNamed('/recipe-detail', arguments: '${meal.recipeId}'); + }, + child: Row( + children: [ + if (meal.cover != null && meal.cover!.isNotEmpty) + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + meal.cover!, + width: 60, + height: 60, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimaryLight, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(CupertinoIcons.photo), + ); + }, + ), + ) + else + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimaryLight, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(CupertinoIcons.photo), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + meal.recipeTitle, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + '${meal.ingredients.length} 种食材', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ), + Icon( + CupertinoIcons.chevron_right, + size: 16, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + ), + ] else + Padding( + padding: const EdgeInsets.symmetric(vertical: DesignTokens.space2), + child: Text( + '点击 + 添加菜谱', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ), + ], + ), + ); + } + + void _showRecipePicker(String mealType) { + showCupertinoModalPopup( + context: context, + builder: (ctx) => Container( + height: 500, + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.background : DesignTokens.background, + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + ), + child: Row( + children: [ + CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () => Navigator.pop(ctx), + child: Text('取消'), + ), + const Spacer(), + Text( + '选择菜谱', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + const SizedBox(width: 60), + ], + ), + ), + Expanded( + child: Obx(() => ListView.separated( + padding: const EdgeInsets.all(DesignTokens.space4), + itemCount: _availableRecipes.length, + separatorBuilder: (_, __) => const SizedBox(height: DesignTokens.space2), + itemBuilder: (context, index) { + final recipe = _availableRecipes[index]; + return GestureDetector( + onTap: () { + _controller.addMealToDay( + _controller.selectedDateKey.value, + mealType, + recipe, + ); + Navigator.pop(ctx); + }, + child: Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Row( + children: [ + Text( + recipe.displayImage, + style: const TextStyle(fontSize: 32), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + recipe.title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (recipe.categoryName != null) ...[ + const SizedBox(height: 4), + Text( + recipe.categoryName!, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ], + ), + ), + Icon( + CupertinoIcons.chevron_right, + size: 16, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + ), + ); + }, + )), + ), + ], + ), + ), + ); + } + + void _showShoppingList() { + final shoppingList = _controller.getShoppingList(); + + showCupertinoModalPopup( + context: context, + builder: (ctx) => Container( + height: 600, + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.background : DesignTokens.background, + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: isDark + ? DarkDesignTokens.glassBorder + : DesignTokens.text3.withValues(alpha: 0.15), + ), + ), + ), + child: Row( + children: [ + CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () => Navigator.pop(ctx), + child: const Text('完成'), + ), + const Spacer(), + Text( + '🛒 购物清单', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + ), + ), + const Spacer(), + CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () { + _controller.clearWeek(); + Navigator.pop(ctx); + }, + child: const Text('清空', style: TextStyle(color: DesignTokens.red)), + ), + ], + ), + ), + Expanded( + child: shoppingList.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('🛒', style: TextStyle(fontSize: 48)), + const SizedBox(height: DesignTokens.space3), + Text( + '购物清单为空', + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ], + ), + ) + : ListView.separated( + padding: const EdgeInsets.all(DesignTokens.space4), + itemCount: shoppingList.length, + separatorBuilder: (_, __) => const SizedBox(height: DesignTokens.space2), + itemBuilder: (context, index) { + final item = shoppingList[index]; + return Container( + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: BorderRadius.circular(DesignTokens.radiusMd), + ), + child: Row( + children: [ + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: const Size(32, 32), + onPressed: () { + _controller.toggleIngredientChecked( + _controller.selectedDateKey.value, + item.name, + ); + }, + child: Icon( + item.checked + ? CupertinoIcons.check_mark_circled_solid + : CupertinoIcons.circle, + color: item.checked + ? (isDark ? DarkDesignTokens.primary : DesignTokens.primary) + : (isDark ? DarkDesignTokens.text3 : DesignTokens.text3), + ), + ), + const SizedBox(width: DesignTokens.space2), + Expanded( + child: Text( + item.display, + style: TextStyle( + fontSize: DesignTokens.fontMd, + decoration: item.checked + ? TextDecoration.lineThrough + : null, + color: item.checked + ? (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + : (isDark ? DarkDesignTokens.text1 : DesignTokens.text1), + ), + ), + ), + ], + ), + ); + }, + ), + ), + ], + ), + ), + ); + } + + String _formatDateKey(DateTime date) { + return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; + } + + bool get isDark => CupertinoTheme.brightnessOf(context) == Brightness.dark; +} diff --git a/lib/src/repositories/recipe_repository.dart b/lib/src/repositories/recipe_repository.dart index a960692..385fae1 100644 --- a/lib/src/repositories/recipe_repository.dart +++ b/lib/src/repositories/recipe_repository.dart @@ -1,6 +1,7 @@ // 2026-04-09 | RecipeRepository | 菜谱数据仓库 | 封装api.php + api_unified.php调用 // 2026-04-10 | API v2.0.0 迁移:unified 接口合并到 api.php?act=unified_*,新增 query/高级查询方法 // 2026-04-10 | 新增 fetchFeedRecipes 方法,首页通过 Repository 层获取推荐数据 +// 2026-04-11 | 修复类型转换问题,添加安全的 Map 转换辅助方法 import 'package:flutter/foundation.dart'; import 'package:mom_kitchen/src/config/api_config.dart'; import 'package:mom_kitchen/src/models/api_response.dart'; @@ -13,6 +14,24 @@ import 'package:mom_kitchen/src/services/api/api_service.dart'; class RecipeRepository { final ApiService _api = ApiService(); + static Map _safeMap(dynamic data) { + if (data is Map) return data; + if (data is Map) return Map.from(data); + throw Exception('Invalid map format'); + } + + static Map? _safeMapOrNull(dynamic data) { + if (data is Map) return data; + if (data is Map) { + try { + return Map.from(data); + } catch (_) { + return null; + } + } + return null; + } + // ─── Feed 推荐数据(首页用) ─── Future> fetchFeedRecipes({ @@ -29,7 +48,11 @@ class RecipeRepository { }; if (refresh) params[ApiConfig.paramRefresh] = '1'; - final response = await _api.get(ApiConfig.feed, queryParameters: params); + final response = await _api.get( + ApiConfig.feed, + queryParameters: params, + forceRefresh: refresh, + ); if (response.data == null) return []; final data = response.data as Map; @@ -118,6 +141,26 @@ class RecipeRepository { return apiResponse.data!; } + // ─── 菜谱详情(含pic_id,使用api.php?act=detail接口) ─── + + Future fetchDetailWithPicId( + int id, { + bool refresh = false, + }) async { + final params = {'act': 'detail', 'id': id}; + if (refresh) params[ApiConfig.paramRefresh] = '1'; + + final response = await _api.get(ApiConfig.recipe, queryParameters: params); + final apiResponse = ApiResponse.fromJson( + response.data as Map, + (data) => RecipeModel.fromJson(data as Map), + ); + if (!apiResponse.isSuccess || apiResponse.data == null) { + throw Exception(apiResponse.message); + } + return apiResponse.data!; + } + // ─── 菜谱完整信息 ─── Future fetchFull( @@ -130,10 +173,12 @@ class RecipeRepository { if (viewnums) params['viewnums'] = 'true'; final response = await _api.get(ApiConfig.recipe, queryParameters: params); - final apiResponse = ApiResponse.fromJson( - response.data as Map, - (data) => RecipeModel.fromJson(data as Map), - ); + + final apiResponse = ApiResponse.fromJson(_safeMap(response.data), (data) { + final map = _safeMapOrNull(data); + if (map == null) throw Exception('Invalid data format'); + return RecipeModel.fromJson(map); + }); if (!apiResponse.isSuccess || apiResponse.data == null) { throw Exception(apiResponse.message); } @@ -158,13 +203,24 @@ class RecipeRepository { 'limit': limit, }, ); - final apiResponse = ApiResponse.fromJson( - response.data as Map, - (data) => PaginatedData.fromJson( - data as Map, - (e) => RecipeModel.fromJson(e), - ), - ); + + Map responseMap; + if (response.data is Map) { + responseMap = response.data as Map; + } else if (response.data is Map) { + responseMap = Map.from(response.data as Map); + } else { + throw Exception('Invalid response format'); + } + + final apiResponse = ApiResponse.fromJson(responseMap, (data) { + if (data is Map) { + return PaginatedData.fromJson(data, (e) { + return RecipeModel.fromJson(e); + }); + } + throw Exception('Invalid data format'); + }); if (!apiResponse.isSuccess || apiResponse.data == null) { throw Exception(apiResponse.message); } @@ -186,13 +242,24 @@ class RecipeRepository { if (search != null) params['search'] = search; final response = await _api.get(ApiConfig.recipe, queryParameters: params); - final apiResponse = ApiResponse.fromJson( - response.data as Map, - (data) => PaginatedData.fromJson( - data as Map, - (e) => IngredientModel.fromJson(e), - ), - ); + + Map responseMap; + if (response.data is Map) { + responseMap = response.data as Map; + } else if (response.data is Map) { + responseMap = Map.from(response.data as Map); + } else { + throw Exception('Invalid response format'); + } + + final apiResponse = ApiResponse.fromJson(responseMap, (data) { + if (data is Map) { + return PaginatedData.fromJson(data, (e) { + return IngredientModel.fromJson(e); + }); + } + throw Exception('Invalid data format'); + }); if (!apiResponse.isSuccess || apiResponse.data == null) { throw Exception(apiResponse.message); } @@ -206,10 +273,25 @@ class RecipeRepository { ApiConfig.recipe, queryParameters: {'act': 'ingredient_detail', 'id': id}, ); - final apiResponse = ApiResponse.fromJson( - response.data as Map, - (data) => IngredientModel.fromJson(data as Map), - ); + + Map responseMap; + if (response.data is Map) { + responseMap = response.data as Map; + } else if (response.data is Map) { + responseMap = Map.from(response.data as Map); + } else { + throw Exception('Invalid response format'); + } + + final apiResponse = ApiResponse.fromJson(responseMap, (data) { + if (data is Map) { + return IngredientModel.fromJson(data); + } + if (data is Map) { + return IngredientModel.fromJson(Map.from(data)); + } + throw Exception('Invalid data format'); + }); if (!apiResponse.isSuccess || apiResponse.data == null) { throw Exception(apiResponse.message); } diff --git a/lib/src/services/api/api_service.dart b/lib/src/services/api/api_service.dart index 206a351..4cad9d5 100644 --- a/lib/src/services/api/api_service.dart +++ b/lib/src/services/api/api_service.dart @@ -8,6 +8,7 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:dio_cache_interceptor_file_store/dio_cache_interceptor_file_store.dart'; import 'package:flutter/foundation.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:mom_kitchen/src/services/log/logger_service.dart'; import 'package:path_provider/path_provider.dart'; import 'package:mom_kitchen/src/services/api/api_exception.dart'; import 'package:mom_kitchen/src/config/api_config.dart'; @@ -65,7 +66,7 @@ class ApiService { _cacheInitialized = true; _cacheInitCompleter!.complete(); } catch (e) { - debugPrint('ApiService: cache init failed: $e'); + LoggerService().error('ApiService: cache init failed: $e'); _dio.interceptors.add(_buildLogInterceptor()); _cacheInitCompleter!.complete(); } diff --git a/lib/src/services/data/cache_service.dart b/lib/src/services/data/cache_service.dart index 07c257f..7e69f48 100644 --- a/lib/src/services/data/cache_service.dart +++ b/lib/src/services/data/cache_service.dart @@ -6,7 +6,6 @@ */ import 'dart:convert'; -import 'package:flutter/foundation.dart'; import 'package:hive_ce/hive.dart'; import 'package:mom_kitchen/src/services/log/logger_service.dart'; @@ -94,7 +93,6 @@ class CacheService { ); await _cacheBox!.put(key, entry.toJson()); - debugPrint('CacheService: Cached $key (TTL: ${ttlSeconds}s)'); } catch (e) { LoggerService().error('CacheService.set failed: $e'); } @@ -182,7 +180,6 @@ class CacheService { Future clear() async { if (!_initialized || _cacheBox == null) return; await _cacheBox!.clear(); - debugPrint('CacheService: Cleared all cache'); } Future clearExpired() async { @@ -207,10 +204,6 @@ class CacheService { for (final key in keysToDelete) { await _cacheBox!.delete(key); } - - if (keysToDelete.isNotEmpty) { - debugPrint('CacheService: Cleared ${keysToDelete.length} expired entries'); - } } int get cacheSize { diff --git a/lib/src/services/data/hive_service.dart b/lib/src/services/data/hive_service.dart index 0b99440..3cdbf36 100644 --- a/lib/src/services/data/hive_service.dart +++ b/lib/src/services/data/hive_service.dart @@ -33,6 +33,9 @@ class HiveService { Box? _searchHistory; Box? _versionBoxInstance; + // 动态 box 缓存,用于通用 get/put 方法 + final Map> _dynamicBoxCache = {}; + bool _initialized = false; bool get isInitialized => _initialized; @@ -86,6 +89,17 @@ class HiveService { _cookingNotes = await _openBoxSafe(_cookingNoteBox); _favorites = await _openBoxSafe(_favoriteBox); _searchHistory = await _openBoxSafe(_searchHistoryBox); + + // 打开动态 boxes(用于通用 get/put 方法) + final bedtimeBox = await _openBoxSafe('bedtime_reminder'); + if (bedtimeBox != null) { + _dynamicBoxCache['bedtime_reminder'] = bedtimeBox; + } + + final weeklyMenuBox = await _openBoxSafe('weekly_menu'); + if (weeklyMenuBox != null) { + _dynamicBoxCache['weekly_menu'] = weeklyMenuBox; + } } Future?> _openBoxSafe(String name, {int maxRetries = 3}) async { @@ -440,6 +454,80 @@ class HiveService { await _searchHistory!.put('history', history); } + // === Generic Key-Value Storage === + + /// 安全获取或打开 box,使用缓存避免重复打开 + Box? _getOrOpenBox(String boxName) { + if (_dynamicBoxCache.containsKey(boxName)) { + return _dynamicBoxCache[boxName]!; + } + + // 检查 box 是否已打开 + if (Hive.isBoxOpen(boxName)) { + final box = Hive.box(boxName); + _dynamicBoxCache[boxName] = box; + LoggerService().debug('Opened and cached dynamic box: $boxName'); + return box; + } + + // box 未打开,返回 null(需要在 init 中预初始化) + LoggerService().warning('Box $boxName is not opened. Please initialize it in init() method.'); + return null; + } + + /// 通用获取方法,从指定 box 获取数据 + /// boxName: box 名称 + /// key: 键名 + /// 返回: 存储的数据,如果不存在返回 null + dynamic get(String boxName, String key) { + if (!_initialized) return null; + try { + final box = _getOrOpenBox(boxName); + if (box == null) return null; + return box.get(key); + } catch (e) { + LoggerService().error('HiveService get error ($boxName/$key): $e'); + return null; + } + } + + /// 通用存储方法,向指定 box 存储数据 + /// boxName: box 名称 + /// key: 键名 + /// value: 要存储的值 + void put(String boxName, String key, dynamic value) { + if (!_initialized) return; + try { + final box = _getOrOpenBox(boxName); + if (box == null) { + LoggerService().warning('Box $boxName is not available for put operation'); + return; + } + box.put(key, value); + LoggerService().debug('HiveService put: $boxName/$key'); + } catch (e) { + LoggerService().error('HiveService put error ($boxName/$key): $e'); + } + } + + /// 删除指定 box 中的数据 + /// boxName: box 名称 + /// key: 键名 + void delete(String boxName, String key) { + if (!_initialized) return; + try { + final box = _getOrOpenBox(boxName); + if (box == null) { + LoggerService().warning('Box $boxName is not available for delete operation'); + return; + } + box.delete(key); + LoggerService().debug('HiveService delete: $boxName/$key'); + } catch (e) { + LoggerService().error('HiveService delete error ($boxName/$key): $e'); + } + } + // === Cleanup === Future compact() async { @@ -460,6 +548,13 @@ class HiveService { await _cookingNotes?.close(); await _favorites?.close(); await _searchHistory?.close(); + + // 清理动态 box 缓存 + for (final box in _dynamicBoxCache.values) { + await box.close(); + } + _dynamicBoxCache.clear(); + _initialized = false; } } diff --git a/lib/src/services/recommendation_service.dart b/lib/src/services/recommendation_service.dart new file mode 100644 index 0000000..bcf80c0 --- /dev/null +++ b/lib/src/services/recommendation_service.dart @@ -0,0 +1,170 @@ +/* + * 文件: recommendation_service.dart + * 名称: 推荐服务 + * 作用: 基于用户偏好、浏览历史、收藏记录的AI菜谱推荐服务 + * 创建: 2026-04-11 + * 更新: 2026-04-11 初始实现 + */ + +import 'package:flutter/foundation.dart'; +import 'package:mom_kitchen/src/controllers/favorites_controller.dart'; +import 'package:mom_kitchen/src/controllers/user/preference_controller.dart'; +import 'package:mom_kitchen/src/models/recipe/recipe_model.dart'; +import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; +import 'package:get/get.dart'; + +class RecommendationService extends GetxService { + final RecipeRepository _recipeRepository = RecipeRepository(); + final PreferenceController _preferenceController = Get.find(); + final FavoritesController _favoritesController = Get.find(); + + // 足迹记录(模拟浏览历史) + final RxList _viewedRecipeIds = [].obs; + List get viewedRecipeIds => _viewedRecipeIds; + + @override + void onInit() { + super.onInit(); + _loadViewHistory(); + } + + void _loadViewHistory() { + // 从本地存储加载浏览历史(简化版) + _viewedRecipeIds.value = []; + } + + void recordView(int recipeId) { + if (!_viewedRecipeIds.contains(recipeId)) { + _viewedRecipeIds.insert(0, recipeId); + if (_viewedRecipeIds.length > 50) { + _viewedRecipeIds.removeLast(); + } + } + } + + /// 获取个性化推荐菜谱 + Future> getPersonalizedRecommendations({int limit = 10}) async { + try { + // 获取所有菜谱 + final recipes = await _recipeRepository.fetchList(); + final allRecipes = recipes.items; + + if (allRecipes.isEmpty) { + return []; + } + + // 计算每个菜谱的推荐分数 + final scoredRecipes = allRecipes.map((recipe) { + double score = 0; + + // 1. 基于用户偏好的分类加分 + if (_preferenceController.preferredCategoryIds.contains(recipe.categoryId)) { + score += 30; + } + + // 2. 基于用户偏好的标签加分 + final tagMatches = recipe.tags.where((tag) => + _preferenceController.preferredTagIds.contains(tag.id) + ).length; + score += tagMatches * 15; + + // 3. 基于收藏记录加分 + if (_favoritesController.isFavorited(recipe.id)) { + score += 20; + } + + // 4. 基于浏览历史加分 + if (_viewedRecipeIds.contains(recipe.id)) { + score += 10; + } + + // 5. 基于过敏原屏蔽扣分 + for (final allergen in recipe.allergens) { + if (_preferenceController.blockedAllergenTypes.contains(allergen)) { + score -= 50; + } + } + + // 6. 基于热度加分 + final views = recipe.statistics?.views ?? 0; + final likes = recipe.statistics?.likes ?? 0; + score += (views + likes * 2) / 100; + + return MapEntry(recipe, score); + }).toList(); + + // 按分数排序 + scoredRecipes.sort((a, b) => b.value.compareTo(a.value)); + + // 过滤掉负分的菜谱(包含过敏原) + final filtered = scoredRecipes.where((e) => e.value > 0).toList(); + + // 返回前N个 + return filtered.take(limit).map((e) => e.key).toList(); + } catch (e) { + debugPrint('Recommendation service error: $e'); + return []; + } + } + + /// 获取相似菜谱推荐 + Future> getSimilarRecipes(int recipeId, {int limit = 5}) async { + try { + final recipes = await _recipeRepository.fetchList(); + final allRecipes = recipes.items; + + final targetRecipe = allRecipes.firstWhereOrNull((r) => r.id == recipeId); + if (targetRecipe == null) return []; + + final scored = allRecipes.where((r) => r.id != recipeId).map((recipe) { + double score = 0; + + // 相同分类 + if (recipe.categoryId == targetRecipe.categoryId) { + score += 40; + } + + // 相同标签 + final commonTags = recipe.tags.where((tag) => + targetRecipe.tags.any((t) => t.id == tag.id) + ).length; + score += commonTags * 20; + + // 相似度计算(标题相似度) + if (recipe.title.contains(targetRecipe.title.split(' ')[0]) || + targetRecipe.title.contains(recipe.title.split(' ')[0])) { + score += 15; + } + + return MapEntry(recipe, score); + }).toList(); + + scored.sort((a, b) => b.value.compareTo(a.value)); + return scored.take(limit).map((e) => e.key).toList(); + } catch (e) { + debugPrint('Similar recipes error: $e'); + return []; + } + } + + /// 获取热门菜谱 + Future> getPopularRecipes({int limit = 10}) async { + try { + final recipes = await _recipeRepository.fetchList(); + final allRecipes = recipes.items; + + final scored = allRecipes.map((recipe) { + final views = recipe.statistics?.views ?? 0; + final likes = recipe.statistics?.likes ?? 0; + final score = views + likes * 2; + return MapEntry(recipe, score); + }).toList(); + + scored.sort((a, b) => b.value.compareTo(a.value)); + return scored.take(limit).map((e) => e.key).toList(); + } catch (e) { + debugPrint('Popular recipes error: $e'); + return []; + } + } +} diff --git a/lib/src/services/ui/theme_service.dart b/lib/src/services/ui/theme_service.dart index 5352e9b..cf5ea0f 100644 --- a/lib/src/services/ui/theme_service.dart +++ b/lib/src/services/ui/theme_service.dart @@ -152,40 +152,19 @@ class ThemeService extends GetxController { } void updateSystemUI() { - if (isStatusBarImmersive.value) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - statusBarColor: primaryColor.value, - statusBarIconBrightness: _getIconBrightness(primaryColor.value), - systemNavigationBarColor: Colors.transparent, - systemNavigationBarIconBrightness: isDarkMode.value - ? Brightness.light - : Brightness.dark, - ), - ); - } else { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.manual, - overlays: SystemUiOverlay.values, - ); - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - statusBarColor: isDarkMode.value ? Colors.black : Colors.white, - statusBarIconBrightness: isDarkMode.value - ? Brightness.light - : Brightness.dark, - systemNavigationBarColor: Colors.transparent, - systemNavigationBarIconBrightness: isDarkMode.value - ? Brightness.light - : Brightness.dark, - ), - ); - } - } - - Brightness _getIconBrightness(Color color) { - return color.computeLuminance() > 0.5 ? Brightness.dark : Brightness.light; + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarIconBrightness: isDarkMode.value + ? Brightness.light + : Brightness.dark, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: isDarkMode.value + ? Brightness.light + : Brightness.dark, + ), + ); } Map get currentTheme { @@ -396,8 +375,13 @@ class ThemeService extends GetxController { barBackgroundColor: Colors.transparent, textTheme: CupertinoTextThemeData( primaryColor: primaryColor.value, - textStyle: TextStyle(fontSize: fontSize.value, color: textColor.value), + textStyle: TextStyle( + inherit: false, + fontSize: fontSize.value, + color: textColor.value, + ), navTitleTextStyle: TextStyle( + inherit: false, fontSize: fontSize.value + 2, fontWeight: FontWeight.w600, color: textColor.value, diff --git a/lib/src/standards/app_pages.dart b/lib/src/standards/app_pages.dart index 5794985..7429ba1 100644 --- a/lib/src/standards/app_pages.dart +++ b/lib/src/standards/app_pages.dart @@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart'; import 'package:mom_kitchen/src/standards/page_validator.dart'; import 'package:mom_kitchen/src/pages/home/home_page.dart'; import 'package:mom_kitchen/src/pages/profile/settings/theme_demo_page.dart'; +import 'package:mom_kitchen/src/pages/tools/tools_center_page.dart'; +import 'package:mom_kitchen/src/pages/tools/ingredient_detail_page.dart'; class AppPages { static final List pages = [ @@ -30,6 +32,28 @@ class AppPages { ], builder: () => const ThemeDemoPage(), ), + PageInfo( + route: '/tools', + name: '工具中心', + description: '工具中心页面', + requiredStandards: [ + StandardCheck.themeColors, + StandardCheck.textColors, + StandardCheck.darkMode, + ], + builder: () => const ToolsCenterPage(), + ), + PageInfo( + route: '/tools/ingredient', + name: '食材详情', + description: '食材营养信息与选购指南', + requiredStandards: [ + StandardCheck.themeColors, + StandardCheck.textColors, + StandardCheck.darkMode, + ], + builder: () => const IngredientDetailPage(), + ), ]; static void registerAll() { diff --git a/lib/src/widgets/base/tap_liquid_glass_nav.dart b/lib/src/widgets/base/tap_liquid_glass_nav.dart index d83e896..7125351 100644 --- a/lib/src/widgets/base/tap_liquid_glass_nav.dart +++ b/lib/src/widgets/base/tap_liquid_glass_nav.dart @@ -72,10 +72,7 @@ class TapLiquidGlassNavigation extends StatelessWidget { try { Get.log('TapLiquidGlassNavigation onTap error: $e'); } catch (_) { - // fallback - debugPrint( - 'TapLiquidGlassNavigation onTap error: $e', - ); + // fallback to silence } // swallow error to avoid app crash } diff --git a/lib/src/widgets/glass/glass_feed_card.dart b/lib/src/widgets/glass/glass_feed_card.dart index 0152973..cf69e88 100644 --- a/lib/src/widgets/glass/glass_feed_card.dart +++ b/lib/src/widgets/glass/glass_feed_card.dart @@ -15,6 +15,7 @@ class GlassFeedCard extends StatelessWidget { final String? category; final String? imageUrl; final int? recipeId; + final int? picId; final int? viewCount; final int? likeCount; final int? recommendCount; @@ -32,6 +33,7 @@ class GlassFeedCard extends StatelessWidget { this.category, this.imageUrl, this.recipeId, + this.picId, this.viewCount, this.likeCount, this.recommendCount, @@ -95,6 +97,7 @@ class GlassFeedCard extends StatelessWidget { aspectRatio: 16 / 9, child: RecipeImage( recipeId: recipeId!, + picId: picId, coverUrl: imageUrl, fit: BoxFit.cover, mode: RecipeImageMode.thumbnail, diff --git a/lib/src/widgets/glass/glass_nav_bar.dart b/lib/src/widgets/glass/glass_nav_bar.dart index 1a53187..fa1753b 100644 --- a/lib/src/widgets/glass/glass_nav_bar.dart +++ b/lib/src/widgets/glass/glass_nav_bar.dart @@ -3,6 +3,7 @@ * 名称: 毛玻璃底部导航栏 * 作用: iOS 26 Liquid Glass 风格的底部导航栏组件 * 更新: 2026-04-10 添加 Badge 支持,支持显示未读数量 + * 更新: 2026-04-11 Bug修复: 去除黑块,实现真正的毛玻璃透明效果 */ import 'dart:ui'; @@ -56,35 +57,25 @@ class GlassNavBar extends StatelessWidget { height: 68, decoration: BoxDecoration( color: isDark - ? DarkDesignTokens.glass.withValues(alpha: 0.78) - : DesignTokens.glass.withValues(alpha: 0.75), + ? CupertinoColors.black.withValues(alpha: 0.25) + : CupertinoColors.white.withValues(alpha: 0.35), borderRadius: BorderRadius.circular(DesignTokens.radiusXl), border: Border.all( color: isDark - ? DarkDesignTokens.glassBorder.withValues(alpha: 0.3) - : DesignTokens.glassBorder.withValues(alpha: 0.4), - width: DesignTokens.glassBorderWidth, + ? CupertinoColors.white.withValues(alpha: 0.15) + : CupertinoColors.black.withValues(alpha: 0.1), + width: 0.5, ), boxShadow: [ BoxShadow( color: isDark - ? DarkDesignTokens.glassShadow - : DesignTokens.glassShadow, - blurRadius: DesignTokens.shadowLgBlur * 1.5, - spreadRadius: 2, - offset: const Offset(0, DesignTokens.shadowMdOffset), + ? CupertinoColors.black.withValues(alpha: 0.3) + : CupertinoColors.black.withValues(alpha: 0.08), + blurRadius: 20, + spreadRadius: 0, + offset: const Offset(0, -4), ), ], - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - (isDark ? DarkDesignTokens.glass : DesignTokens.glass) - .withValues(alpha: 0.1), - (isDark ? DarkDesignTokens.glass : DesignTokens.glass) - .withValues(alpha: 0.05), - ], - ), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, diff --git a/lib/src/widgets/navigation_widgets.dart b/lib/src/widgets/navigation_widgets.dart index 4433fdc..47cd19e 100644 --- a/lib/src/widgets/navigation_widgets.dart +++ b/lib/src/widgets/navigation_widgets.dart @@ -10,7 +10,6 @@ import 'package:mom_kitchen/src/controllers/favorites_controller.dart'; import 'package:mom_kitchen/src/services/core/app_service.dart'; import 'package:mom_kitchen/src/services/ui/theme_service.dart'; import 'package:mom_kitchen/src/widgets/glass/glass_nav_bar.dart'; -import 'package:mom_kitchen/src/widgets/states/offline_banner.dart'; class MainTabView extends StatelessWidget { const MainTabView({super.key}); @@ -60,35 +59,43 @@ class MainTabView extends StatelessWidget { return Container( color: bgColor, - child: Column( + child: Stack( children: [ - const OfflineBanner(), - Expanded( - child: HeroMode( - enabled: false, - child: IndexedStack( - index: nav.currentIndex.value, - children: pages, + Column( + children: [ + Expanded( + child: HeroMode( + enabled: false, + child: IndexedStack( + index: nav.currentIndex.value, + children: pages, + ), + ), ), - ), + ], ), - Obx(() { - final themeService = AppService.instance.theme; - final style = themeService.bottomBarStyle.value; - if (style == BottomBarStyle.edge) { - return _buildEdgeNavBar( - nav.currentIndex.value, - navItems, - isDark, - (i) => nav.switchPage(i), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Obx(() { + final themeService = AppService.instance.theme; + final style = themeService.bottomBarStyle.value; + if (style == BottomBarStyle.edge) { + return _buildEdgeNavBar( + nav.currentIndex.value, + navItems, + isDark, + (i) => nav.switchPage(i), + ); + } + return GlassNavBar( + currentIndex: nav.currentIndex.value, + items: navItems, + onTap: (i) => nav.switchPage(i), ); - } - return GlassNavBar( - currentIndex: nav.currentIndex.value, - items: navItems, - onTap: (i) => nav.switchPage(i), - ); - }), + }), + ), ], ), ); diff --git a/lib/src/widgets/recipe_image.dart b/lib/src/widgets/recipe_image.dart index e6d1918..f031741 100644 --- a/lib/src/widgets/recipe_image.dart +++ b/lib/src/widgets/recipe_image.dart @@ -4,8 +4,14 @@ * 作用: 支持缓存+多级fallback+缩略图压缩+点击查看原图的菜谱图片显示 * 创建: 2026-04-11 * 更新: 2026-04-11 增加缩略图压缩模式+点击查看原图+缓存管理 + * 更新: 2026-04-11 新增 picId 参数,使用API返回的 pic_id 构建图片URL(替代 recipeId) + * 更新: 2026-04-11 完整Fallback链:coverUrl→{picId}a.jpg→b.jpg→.jpg→本地error.png→空白 + * 更新: 2026-04-11 彻底修复 Invalid image 异常泄露: + * - 下载后增加完整解码验证(非仅头部校验) + * - build() 完全异常安全,使用独立显示状态隔离 + * - _buildErrorWidget 永远不引用可能无效的 _imageBytes + * - 压缩操作使用 compute() 隔离防止 UI 卡死 * - * Fallback链: coverUrl → {id}a.jpg → {id}b.jpg → {id}.jpg → back.png → 本地error.png → 空白 * 缓存策略: 内存缓存(24h) + 磁盘缓存(7天, path_provider临时目录) * 缩略图: 列表模式自动压缩至 thumbnailMaxPx 指定尺寸,减少流量和内存 * 原图: 详情页或点击时加载全尺寸图片 @@ -16,7 +22,7 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart' show rootBundle; import 'package:path_provider/path_provider.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; @@ -24,6 +30,7 @@ enum RecipeImageMode { thumbnail, full } class RecipeImage extends StatefulWidget { final int recipeId; + final int? picId; final double? width; final double? height; final BoxFit fit; @@ -36,6 +43,7 @@ class RecipeImage extends StatefulWidget { const RecipeImage({ super.key, required this.recipeId, + this.picId, this.width, this.height, this.fit = BoxFit.cover, @@ -49,6 +57,7 @@ class RecipeImage extends StatefulWidget { const RecipeImage.full({ super.key, required this.recipeId, + this.picId, this.width, this.height, this.fit = BoxFit.cover, @@ -65,17 +74,23 @@ class RecipeImage extends StatefulWidget { class _RecipeImageState extends State { static final Map _memoryCache = {}; static const String _picBase = 'http://eat.wktyl.com/api/assets/pic'; - static const String _backUrl = 'http://eat.wktyl.com/api/assets/back.png'; + static const String _localErrorAsset = 'assets/photos/error.png'; + + static Uint8List? _localErrorBytes; int _fallbackIndex = 0; bool _isLoading = true; - bool _hasError = false; + Uint8List? _imageBytes; String? _currentUrl; bool _isShowingOriginal = false; - List get _fallbackUrls { - final id = widget.recipeId; + bool _displayHasError = false; + + int get _effectivePicId => widget.picId ?? widget.recipeId; + + List get _networkUrls { + final id = _effectivePicId; final urls = []; if (widget.coverUrl != null && widget.coverUrl!.isNotEmpty) { urls.add(widget.coverUrl!); @@ -84,7 +99,6 @@ class _RecipeImageState extends State { '$_picBase/${id}a.jpg', '$_picBase/${id}b.jpg', '$_picBase/$id.jpg', - _backUrl, ]); return urls; } @@ -95,18 +109,28 @@ class _RecipeImageState extends State { @override void initState() { super.initState(); + _preloadLocalError(); _loadImage(); } + Future _preloadLocalError() async { + if (_localErrorBytes != null) return; + try { + final data = await rootBundle.load(_localErrorAsset); + _localErrorBytes = data.buffer.asUint8List(); + } catch (_) {} + } + @override void didUpdateWidget(RecipeImage oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.recipeId != widget.recipeId || + oldWidget.picId != widget.picId || oldWidget.coverUrl != widget.coverUrl || oldWidget.mode != widget.mode) { _fallbackIndex = 0; _isLoading = true; - _hasError = false; + _displayHasError = false; _imageBytes = null; _isShowingOriginal = false; _loadImage(); @@ -114,28 +138,29 @@ class _RecipeImageState extends State { } Future _loadImage() async { - final urls = _fallbackUrls; - if (_fallbackIndex >= urls.length) { - if (mounted) { - setState(() { - _isLoading = false; - _hasError = true; - }); - } - return; - } + final networkUrls = _networkUrls; + if (_fallbackIndex < networkUrls.length) { + await _loadNetworkImage(networkUrls); + } else if (_fallbackIndex == networkUrls.length) { + await _loadLocalErrorFallback(); + } else { + _showBlank(); + } + } + + Future _loadNetworkImage(List urls) async { final url = urls[_fallbackIndex]; _currentUrl = url; final cacheKey = '$_cacheKeyPrefix$url'; final cached = _getFromMemoryCache(cacheKey); if (cached != null) { - if (mounted) { + if (mounted && _currentUrl == url) { setState(() { _imageBytes = cached; _isLoading = false; - _hasError = false; + _displayHasError = false; }); } return; @@ -148,7 +173,7 @@ class _RecipeImageState extends State { setState(() { _imageBytes = diskCached; _isLoading = false; - _hasError = false; + _displayHasError = false; }); } return; @@ -168,6 +193,16 @@ class _RecipeImageState extends State { final rawData = bytes.toBytes(); client.close(); + if (rawData.length < 100) { + _tryNextFallback(); + return; + } + + if (!_isValidImageData(rawData)) { + _tryNextFallback(); + return; + } + Uint8List finalData; if (widget.mode == RecipeImageMode.thumbnail && !_isShowingOriginal) { finalData = await _compressImage( @@ -185,7 +220,7 @@ class _RecipeImageState extends State { setState(() { _imageBytes = finalData; _isLoading = false; - _hasError = false; + _displayHasError = false; }); } } else { @@ -193,11 +228,47 @@ class _RecipeImageState extends State { _tryNextFallback(); } } catch (e) { - debugPrint('RecipeImage load error ($url): $e'); _tryNextFallback(); } } + Future _loadLocalErrorFallback() async { + if (_localErrorBytes != null) { + if (mounted) { + setState(() { + _imageBytes = null; + _isLoading = false; + _displayHasError = true; + }); + } + return; + } + + try { + final data = await rootBundle.load(_localErrorAsset); + _localErrorBytes = data.buffer.asUint8List(); + if (mounted) { + setState(() { + _imageBytes = null; + _isLoading = false; + _displayHasError = true; + }); + } + } catch (e) { + _showBlank(); + } + } + + void _showBlank() { + if (mounted) { + setState(() { + _isLoading = false; + _displayHasError = true; + _imageBytes = null; + }); + } + } + Future _compressImage(Uint8List data, int maxPx) async { try { final codec = await ui.instantiateImageCodecFromBuffer( @@ -239,7 +310,6 @@ class _RecipeImageState extends State { if (byteData == null) return data; return byteData.buffer.asUint8List(); } catch (e) { - debugPrint('RecipeImage compress error: $e'); return data; } } @@ -251,11 +321,48 @@ class _RecipeImageState extends State { } } + void _onDisplayError() { + if (!_displayHasError && mounted) { + _displayHasError = true; + _imageBytes = null; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + _tryNextFallback(); + } + }); + } + } + + static bool _isValidImageData(Uint8List data) { + if (data.length < 8) return false; + final header = data.sublist(0, 8); + final isJpeg = header[0] == 0xFF && header[1] == 0xD8 && header[2] == 0xFF; + final isPng = + header[0] == 0x89 && + header[1] == 0x50 && + header[2] == 0x4E && + header[3] == 0x47; + final isGif = + header[0] == 0x47 && + header[1] == 0x49 && + header[2] == 0x46 && + header[3] == 0x38; + final isWebP = + header[0] == 0x52 && + header[1] == 0x49 && + header[2] == 0x46 && + header[3] == 0x46; + final isBmp = header[0] == 0x42 && header[1] == 0x4D; + return isJpeg || isPng || isGif || isWebP || isBmp; + } + Future _loadOriginalImage() async { if (_isShowingOriginal) return; setState(() { _isShowingOriginal = true; _isLoading = true; + _displayHasError = false; _fallbackIndex = 0; _imageBytes = null; }); @@ -331,16 +438,10 @@ class _RecipeImageState extends State { if (_isLoading) { child = _buildLoadingWidget(isDark); - } else if (_hasError || _imageBytes == null) { + } else if (_displayHasError || _imageBytes == null) { child = _buildErrorWidget(isDark); } else { - child = Image.memory( - _imageBytes!, - width: widget.width, - height: widget.height, - fit: widget.fit, - errorBuilder: (_, __, ___) => _buildErrorWidget(isDark), - ); + child = _buildSafeImageWidget(isDark); } if (widget.tapToOriginal && @@ -352,7 +453,7 @@ class _RecipeImageState extends State { fit: StackFit.passthrough, children: [ child, - if (!_isLoading && !_hasError && _imageBytes != null) + if (!_isLoading && !_displayHasError && _imageBytes != null) Positioned( right: 6, bottom: 6, @@ -397,6 +498,19 @@ class _RecipeImageState extends State { return child; } + Widget _buildSafeImageWidget(bool isDark) { + return Image.memory( + _imageBytes!, + width: widget.width, + height: widget.height, + fit: widget.fit, + errorBuilder: (_, __, ___) { + _onDisplayError(); + return _buildErrorWidget(isDark); + }, + ); + } + Widget _buildLoadingWidget(bool isDark) { return Container( width: widget.width, @@ -414,6 +528,20 @@ class _RecipeImageState extends State { } Widget _buildErrorWidget(bool isDark) { + if (_localErrorBytes != null) { + return Image.memory( + _localErrorBytes!, + width: widget.width, + height: widget.height, + fit: widget.fit, + errorBuilder: (_, __, ___) => _buildBlankPlaceholder(isDark), + ); + } + + return _buildBlankPlaceholder(isDark); + } + + Widget _buildBlankPlaceholder(bool isDark) { return Container( width: widget.width, height: widget.height, @@ -452,6 +580,7 @@ class _CacheEntry { class RecipeImageCache { static Future clearCache() async { _RecipeImageState._memoryCache.clear(); + _RecipeImageState._localErrorBytes = null; try { final dir = await getTemporaryDirectory(); final cacheDir = Directory('${dir.path}/recipe_images'); @@ -480,7 +609,9 @@ class RecipeImageCache { static Future getCacheSizeText() async { final bytes = await getCacheSize(); if (bytes < 1024) return '$bytes B'; - if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; + if (bytes < 1024 * 1024) { + return '${(bytes / 1024).toStringAsFixed(1)} KB'; + } return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; } } diff --git a/packages/fluttertoast_ohos/ohos/build/default/intermediates/merge_profile/default/module.json b/packages/fluttertoast_ohos/ohos/build/default/intermediates/merge_profile/default/module.json index 9adbccd..77cff69 100644 --- a/packages/fluttertoast_ohos/ohos/build/default/intermediates/merge_profile/default/module.json +++ b/packages/fluttertoast_ohos/ohos/build/default/intermediates/merge_profile/default/module.json @@ -2,8 +2,8 @@ "app": { "bundleName": "com.example.ohos", "debug": true, - "versionCode": 61, - "versionName": "0.61.0", + "versionCode": 69, + "versionName": "0.69.0", "minAPIVersion": 50100018, "targetAPIVersion": 60002022, "apiReleaseType": "Release", diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/BuildProfile.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/BuildProfile.ets deleted file mode 100644 index c889882..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/BuildProfile.ets +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Use these variables when you tailor your ArkTS code. They must be of the const type. - */ -export const HAR_VERSION = '1.0.0-389fc59b68'; -export const BUILD_MODE_NAME = 'debug'; -export const DEBUG = true; -export const TARGET_NAME = 'default'; - -/** - * BuildProfile Class is used only for compatibility purposes. - */ -export default class BuildProfile { - static readonly HAR_VERSION = HAR_VERSION; - static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; - static readonly DEBUG = DEBUG; - static readonly TARGET_NAME = TARGET_NAME; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/build-profile.json5 b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/build-profile.json5 deleted file mode 100644 index e395590..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/build-profile.json5 +++ /dev/null @@ -1,40 +0,0 @@ - - -{ - "apiType": "stageMode", - "buildOption": { - "sourceOption": { - "workers": [ - "./src/main/ets/embedding/engine/workers/PlatformChannelWorker.ets" - ] - }, - "nativeLib": { - "debugSymbol": { - "strip": false, - "exclude": [] - } - } - }, - "buildOptionSet": [ - { - "name": "release", - "arkOptions": { - "obfuscation": { - "ruleOptions": { - "enable": false, - "files": [ - "./obfuscation-rules.txt" - ] - }, - "consumerFiles": ["./consumer-rules.txt"] - } - } - }, - ], - "targets": [ - { - "name": "default", - "runtimeOS": "HarmonyOS" - } - ] -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/consumer-rules.txt b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/consumer-rules.txt deleted file mode 100644 index 33b1039..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/consumer-rules.txt +++ /dev/null @@ -1,4 +0,0 @@ -# flutter_ohos 在混淆时需要保留的代码 --keep-property-name -flutter -native* diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/hvigorfile.ts b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/hvigorfile.ts deleted file mode 100644 index f2c2731..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/hvigorfile.ts +++ /dev/null @@ -1,3 +0,0 @@ - -// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. -export { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/index.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/index.ets deleted file mode 100644 index 2ac7c12..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/index.ets +++ /dev/null @@ -1,108 +0,0 @@ - - -export { default as FlutterInjector } from './src/main/ets/FlutterInjector'; -export { default as FlutterPluginRegistry } from './src/main/ets/app/FlutterPluginRegistry'; -export { default as FlutterComponent } from './src/main/ets/component/FlutterComponent'; -export { default as FlutterEngine } from './src/main/ets/embedding/engine/FlutterEngine'; -export { default as FlutterEngineCache } from './src/main/ets/embedding/engine/FlutterEngineCache'; -export { default as FlutterEngineConnectionRegistry } from './src/main/ets/embedding/engine/FlutterEngineConnectionRegistry'; -export { default as FlutterEngineGroup } from './src/main/ets/embedding/engine/FlutterEngineGroup'; -export { default as FlutterEnginePreload } from './src/main/ets/embedding/engine/FlutterEnginePreload'; -export { default as FlutterEngineGroupCache } from './src/main/ets/embedding/engine/FlutterEngineGroupCache'; -export { default as FlutterNapi } from './src/main/ets/embedding/engine/FlutterNapi'; -export * from './src/main/ets/embedding/engine/FlutterOverlaySurface'; -export { default as FlutterShellArgs } from './src/main/ets/embedding/engine/FlutterShellArgs'; -export { default as DartExecutor } from './src/main/ets/embedding/engine/dart/DartExecutor'; -export * from './src/main/ets/embedding/engine/dart/DartMessenger'; -export * from './src/main/ets/embedding/engine/dart/PlatformMessageHandler'; -export { default as ApplicationInfoLoader } from './src/main/ets/embedding/engine/loader/ApplicationInfoLoader'; -export { default as FlutterApplicationInfo } from './src/main/ets/embedding/engine/loader/FlutterApplicationInfo'; -export { default as FlutterLoader } from './src/main/ets/embedding/engine/loader/FlutterLoader'; -export * from './src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorView'; -export * from './src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorsStack'; -export * from './src/main/ets/embedding/engine/plugins/FlutterPlugin'; -export { default as PluginRegistry } from './src/main/ets/embedding/engine/plugins/PluginRegistry'; -export { default as AbilityAware } from './src/main/ets/embedding/engine/plugins/ability/AbilityAware'; -export { default as AbilityControlSurface } from './src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface'; -export * from './src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding'; -export * from './src/main/ets/embedding/engine/renderer/FlutterRenderer'; -export * from './src/main/ets/embedding/engine/renderer/FlutterUiDisplayListener'; -export { default as AccessibilityChannel } from './src/main/ets/embedding/engine/systemchannels/AccessibilityChannel'; -export { default as KeyEventChannel } from './src/main/ets/embedding/engine/systemchannels/KeyEventChannel'; -export { default as LifecycleChannel } from './src/main/ets/embedding/engine/systemchannels/LifecycleChannel'; -export { default as LocalizationChannel } from './src/main/ets/embedding/engine/systemchannels/LocalizationChannel'; -export { default as MouseCursorChannel } from './src/main/ets/embedding/engine/systemchannels/MouseCursorChannel'; -export { default as DisplayMetricsChannel } from './src/main/ets/embedding/engine/systemchannels/DisplayMetricsChannel'; -export { default as NavigationChannel } from './src/main/ets/embedding/engine/systemchannels/NavigationChannel'; -export { default as PlatformChannel } from './src/main/ets/embedding/engine/systemchannels/PlatformChannel'; -export { default as PlatformViewsChannel } from './src/main/ets/embedding/engine/systemchannels/PlatformViewsChannel'; -export { default as RestorationChannel } from './src/main/ets/embedding/engine/systemchannels/RestorationChannel'; -export { default as SettingsChannel } from './src/main/ets/embedding/engine/systemchannels/SettingsChannel'; -export { default as SystemChannel } from './src/main/ets/embedding/engine/systemchannels/SystemChannel'; -export { default as TestChannel } from './src/main/ets/embedding/engine/systemchannels/TestChannel'; -export { default as TextInputChannel } from './src/main/ets/embedding/engine/systemchannels/TextInputChannel'; -export { default as NativeVsyncChannel } from './src/main/ets/embedding/engine/systemchannels/NativeVsyncChannel'; -export { default as ExclusiveAppComponent } from './src/main/ets/embedding/ohos/ExclusiveAppComponent'; -export * from './src/main/ets/embedding/ohos/FlutterAbility'; -export * from './src/main/ets/embedding/ohos/FlutterAbilityAndEntryDelegate'; -export { default as FlutterAbilityLaunchConfigs } from './src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs'; -export { default as FlutterEngineConfigurator } from './src/main/ets/embedding/ohos/FlutterEngineConfigurator'; -export { default as FlutterEngineProvider } from './src/main/ets/embedding/ohos/FlutterEngineProvider'; -export { default as FlutterEntry } from './src/main/ets/embedding/ohos/FlutterEntry'; -export { default as FlutterManager, DragDropCallback as DragDropCallback } from './src/main/ets/embedding/ohos/FlutterManager'; -export * from './src/main/ets/embedding/ohos/FlutterPage'; -export { default as KeyboardManager } from './src/main/ets/embedding/ohos/KeyboardManager'; -export { default as OhosTouchProcessor } from './src/main/ets/embedding/ohos/OhosTouchProcessor'; -export { default as Settings } from './src/main/ets/embedding/ohos/Settings'; -export * from './src/main/ets/embedding/ohos/TouchEventTracker'; -export { default as WindowInfoRepositoryCallbackAdapterWrapper } from './src/main/ets/embedding/ohos/WindowInfoRepositoryCallbackAdapterWrapper'; -export { default as PlatformPlugin } from './src/main/ets/plugin/PlatformPlugin'; -export { default as BasicMessageChannel, Reply } from './src/main/ets/plugin/common/BasicMessageChannel'; -export { default as BinaryCodec } from './src/main/ets/plugin/common/BinaryCodec'; -export * from './src/main/ets/plugin/common/BinaryMessenger'; -export { default as EventChannel, StreamHandler, EventSink } from './src/main/ets/plugin/common/EventChannel'; -export { default as FlutterException } from './src/main/ets/plugin/common/FlutterException'; -export { default as JSONMessageCodec } from './src/main/ets/plugin/common/JSONMessageCodec'; -export { default as JSONMethodCodec } from './src/main/ets/plugin/common/JSONMethodCodec'; -export { default as MessageCodec } from './src/main/ets/plugin/common/MessageCodec'; -export { default as MethodCall } from './src/main/ets/plugin/common/MethodCall'; -export * from './src/main/ets/plugin/common/MethodChannel'; -export { default as MethodChannel } from './src/main/ets/plugin/common/MethodChannel'; -export { default as MethodCodec } from './src/main/ets/plugin/common/MethodCodec'; -export { default as BackgroundBasicMessageChannel } from './src/main/ets/plugin/common/BackgroundBasicMessageChannel'; -export { default as BackgroundMethodChannel} from './src/main/ets/plugin/common/BackgroundMethodChannel' -export { default as SendableBinaryCodec} from './src/main/ets/plugin/common/SendableBinaryCodec' -export { default as SendableJSONMessageCodec} from './src/main/ets/plugin/common/SendableJSONMessageCodec' -export { default as SendableJSONMethodCodec} from './src/main/ets/plugin/common/SendableJSONMethodCodec' -export { default as SendableMessageHandler} from './src/main/ets/plugin/common/SendableMessageHandler' -export { default as SendableMethodCallHandler} from './src/main/ets/plugin/common/SendableMethodCallHandler' -export { default as SendableMethodCodec} from './src/main/ets/plugin/common/SendableMethodCodec' -export { default as SendableStandardMessageCodec } from './src/main/ets/plugin/common/SendableStandardMessageCodec'; -export { default as SendableStandardMethodCodec } from './src/main/ets/plugin/common/SendableStandardMethodCodec'; -export { default as SendableStringCodec} from './src/main/ets/plugin/common/SendableStringCodec' -export { default as StandardMessageCodec } from './src/main/ets/plugin/common/StandardMessageCodec'; -export { default as StandardMethodCodec } from './src/main/ets/plugin/common/StandardMethodCodec'; -export { default as StringCodec } from './src/main/ets/plugin/common/StringCodec'; -export * from './src/main/ets/plugin/editing/ListenableEditingState'; -export * from './src/main/ets/plugin/editing/TextEditingDelta'; -export { default as TextInputPlugin } from './src/main/ets/plugin/editing/TextInputPlugin'; -export { default as LocalizationPlugin } from './src/main/ets/plugin/localization/LocalizationPlugin'; -export { default as MouseCursorPlugin } from './src/main/ets/plugin/mouse/MouseCursorPlugin'; -export { default as PlatformView } from './src/main/ets/plugin/platform/PlatformView'; -export { default as PlatformViewFactory } from './src/main/ets/plugin/platform/PlatformViewFactory'; -export { default as PlatformViewRegistry } from './src/main/ets/plugin/platform/PlatformViewRegistry'; -export { default as PlatformViewRegistryImpl } from './src/main/ets/plugin/platform/PlatformViewRegistryImpl'; -export * from './src/main/ets/plugin/platform/PlatformViewWrapper'; -export { default as PlatformViewsController } from './src/main/ets/plugin/platform/PlatformViewsController'; -export * from './src/main/ets/view/FlutterCallbackInformation'; -export { default as FlutterRunArguments } from './src/main/ets/view/FlutterRunArguments'; -export * from './src/main/ets/view/FlutterView'; -export * from './src/main/ets/view/TextureRegistry'; -export * from './src/main/ets/util/ByteBuffer'; -export { default as Log } from './src/main/ets/util/Log'; -export { default as MessageChannelUtils } from './src/main/ets/util/MessageChannelUtils'; -export { default as PathUtils } from './src/main/ets/util/PathUtils'; -export { default as StringUtils } from './src/main/ets/util/StringUtils'; -export { default as ToolUtils } from './src/main/ets/util/ToolUtils'; -export * from './src/main/ets/util/TraceSection'; -export { default as Any } from './src/main/ets/plugin/common/Any'; diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/obfuscation-rules.txt b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/obfuscation-rules.txt deleted file mode 100644 index eb5c766..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/obfuscation-rules.txt +++ /dev/null @@ -1,18 +0,0 @@ -# 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 diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/oh-package.json5 b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/oh-package.json5 deleted file mode 100644 index 4d9d3c9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/oh-package.json5 +++ /dev/null @@ -1 +0,0 @@ -{"license":"Apache-2.0","author":"","name":"@ohos/flutter_ohos","description":"The embedder of flutter in ohos.","main":"index.ets","version":"1.0.0-389fc59b68","dependencies":{},"devDependencies":{"@types/libflutter.so":"file:./src/main/cpp/types/libflutter"},"metadata":{"workers":["./src/main/ets/embedding/engine/workers/PlatformChannelWorker.ets"],"sourceRoots":["./src/main"],"debug":true},"compatibleSdkVersionStage":"beta1","compatibleSdkVersion":12,"compatibleSdkType":"HarmonyOS","obfuscated":false} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/cpp/types/libflutter/index.d.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/cpp/types/libflutter/index.d.ets deleted file mode 100644 index 20dec02..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/cpp/types/libflutter/index.d.ets +++ /dev/null @@ -1,532 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import common from '@ohos.app.ability.common'; -import resourceManager from '@ohos.resourceManager'; -import image from '@ohos.multimedia.image'; -import FlutterNapi from '../../../ets/embedding/engine/FlutterNapi'; -import { ByteBuffer } from '../../../ets/util/ByteBuffer'; -import { FlutterCallbackInformation } from '../../../ets/view/FlutterCallbackInformation'; - -/** - * Updates the refresh rate for the Flutter engine. - * @param rate - The refresh rate value to set - */ -export const nativeUpdateRefreshRate: ( - rate: number -) => void; - -/** - * Initializes SkFontMgr::RefDefault() to prefetch the default font manager. - * This should be called before using fonts in the Flutter engine. - */ -export const nativePrefetchDefaultFontManager: () => void; - -/** - * Checks and reloads fonts for the specified shell holder. - * @param nativeShellHolderId - The ID of the native shell holder - */ -export const nativeCheckAndReloadFont: (nativeShellHolderId: number) => void; - -/** - * Updates the size of the Flutter view. - * @param width - The new width in pixels - * @param height - The new height in pixels - */ -export const nativeUpdateSize: ( - width: number, - height: number -) => void; - -/** - * Updates the pixel density of the display. - * @param densityPixels - The pixel density value (dots per inch) - */ -export const nativeUpdateDensity: ( - densityPixels: number -) => void; - -/** - * Initializes the Dart VM and Flutter engine. - * @param context - The application context - * @param args - Command line arguments for the Flutter engine - * @param bundlePath - Path to the Flutter bundle - * @param appStoragePath - Path to the application storage directory - * @param engineCachesPath - Path to the engine caches directory - * @param initTimeMillis - Initialization time in milliseconds - * @param productModel - Product model identifier - * @returns The native shell holder ID, or null if initialization fails - */ -export const nativeInit: ( - context: common.Context, - args: Array, - bundlePath: string, - appStoragePath: string, - engineCachesPath: string, - initTimeMillis: number, - productModel: string -) => number | null; - -/** - * Attaches a FlutterNapi instance to the engine. - * @param napi - The FlutterNapi instance to attach - * @returns The native shell holder ID - */ -export const nativeAttach: (napi: FlutterNapi) => number; - -/** - * Spawns a new Flutter shell instance. - * @param nativeSpawningShellId - The ID of the spawning shell, or null for a new shell - * @param entrypointFunctionName - Name of the entrypoint function - * @param pathToEntrypointFunction - Path to the entrypoint function - * @param initialRoute - Initial route for navigation - * @param entrypointArgs - Arguments to pass to the entrypoint function - * @param napi - The FlutterNapi instance - * @returns The native shell holder ID of the spawned shell - */ -export const nativeSpawn: ( - nativeSpawningShellId: number | null, - entrypointFunctionName: string, - pathToEntrypointFunction: string, - initialRoute: string, - entrypointArgs: Array, - napi: FlutterNapi -) => number; - -/** - * Runs a Flutter bundle and snapshot from a library. - * @param nativeShellHolderId - The ID of the native shell holder - * @param bundlePath - Path to the Flutter bundle - * @param entrypointFunctionName - Name of the entrypoint function - * @param pathToEntrypointFunction - Path to the entrypoint function - * @param assetManager - Resource manager for accessing assets - * @param entrypointArgs - Arguments to pass to the entrypoint function - */ -export const nativeRunBundleAndSnapshotFromLibrary: ( - nativeShellHolderId: number, - bundlePath: string, - entrypointFunctionName: string, - pathToEntrypointFunction: string, - assetManager: resourceManager.ResourceManager, - entrypointArgs: Array -) => void; - -/** - * Sends a data-carrying response to a platform message received from Dart. - * @param nativeShellHolderId - The ID of the native shell holder - * @param responseId - The response ID for the platform message - * @param message - The message data as an ArrayBuffer - * @param position - The position in the message buffer - */ -export const nativeInvokePlatformMessageResponseCallback: (nativeShellHolderId: number, responseId: number, message: ArrayBuffer, position: number) => void; - -/** - * Sends an empty response to a platform message received from Dart. - * @param nativeShellHolderId - The ID of the native shell holder - * @param responseId - The response ID for the platform message - */ -export const nativeInvokePlatformMessageEmptyResponseCallback: (nativeShellHolderId: number, responseId: number) => void; - -/** - * Sends a data-carrying platform message to Dart. - * @param nativeShellHolderId - The ID of the native shell holder - * @param channel - The channel name for the platform message - * @param message - The message data as an ArrayBuffer - * @param position - The position in the message buffer - * @param responseId - The response ID for the platform message - */ -export const nativeDispatchPlatformMessage: (nativeShellHolderId: number, channel: String, message: ArrayBuffer, position: number, responseId: number) => void; - -/** - * Sends an empty platform message to Dart. - * @param nativeShellHolderId - The ID of the native shell holder - * @param channel - The channel name for the platform message - * @param responseId - The response ID for the platform message - */ -export const nativeDispatchEmptyPlatformMessage: (nativeShellHolderId: number, channel: String, responseId: number) => void; - -/** - * Sets the viewport metrics for the Flutter view. - * @param nativeShellHolderId - The ID of the native shell holder - * @param devicePixelRatio - The device pixel ratio - * @param physicalWidth - Physical width in pixels - * @param physicalHeight - Physical height in pixels - * @param physicalPaddingTop - Top padding in physical pixels - * @param physicalPaddingRight - Right padding in physical pixels - * @param physicalPaddingBottom - Bottom padding in physical pixels - * @param physicalPaddingLeft - Left padding in physical pixels - * @param physicalViewInsetTop - Top view inset in physical pixels - * @param physicalViewInsetRight - Right view inset in physical pixels - * @param physicalViewInsetBottom - Bottom view inset in physical pixels - * @param physicalViewInsetLeft - Left view inset in physical pixels - * @param systemGestureInsetTop - Top system gesture inset - * @param systemGestureInsetRight - Right system gesture inset - * @param systemGestureInsetBottom - Bottom system gesture inset - * @param systemGestureInsetLeft - Left system gesture inset - * @param physicalTouchSlop - Physical touch slop value - * @param displayFeaturesBounds - Array of display feature bounds - * @param displayFeaturesType - Array of display feature types - * @param displayFeaturesState - Array of display feature states - */ -export const nativeSetViewportMetrics: (nativeShellHolderId: number, devicePixelRatio: number, physicalWidth: number - , physicalHeight: number, physicalPaddingTop: number, physicalPaddingRight: number - , physicalPaddingBottom: number, physicalPaddingLeft: number, physicalViewInsetTop: number - , physicalViewInsetRight: number, physicalViewInsetBottom: number, physicalViewInsetLeft: number - , systemGestureInsetTop: number, systemGestureInsetRight: number, systemGestureInsetBottom: number - , systemGestureInsetLeft: number, physicalTouchSlop: number, displayFeaturesBounds: Array - , displayFeaturesType: Array, displayFeaturesState: Array) => void; - -/** - * Gets the system languages and populates the provided array. - * @param nativeShellHolderId - The ID of the native shell holder - * @param languages - Array to be populated with system language codes - */ -export const nativeGetSystemLanguages: (nativeShellHolderId: number, languages: Array) => void; - -/** - * Attaches a Flutter engine to an XComponent. - * @param xcomponentId - The ID of the XComponent - * @param nativeShellHolderId - The ID of the native shell holder - */ -export const nativeXComponentAttachFlutterEngine: (xcomponentId: string, nativeShellHolderId: number) => void; - -/** - * Called before drawing the XComponent. - * @param xcomponentId - The ID of the XComponent - * @param nativeShellHolderId - The ID of the native shell holder - * @param width - The width of the component - * @param height - The height of the component - */ -export const nativeXComponentPreDraw: (xcomponentId: string, nativeShellHolderId: number, width: number, height: number) => void; - -/** - * Detaches a Flutter engine from an XComponent. - * @param xcomponentId - The ID of the XComponent - * @param nativeShellHolderId - The ID of the native shell holder - */ -export const nativeXComponentDetachFlutterEngine: (xcomponentId: string, nativeShellHolderId: number) => void; - -/** - * Dispatches a mouse wheel event to the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param xcomponentId - The ID of the XComponent - * @param eventType - The type of the mouse wheel event - * @param fingerId - The finger ID for the event - * @param globalX - Global X coordinate - * @param globalY - Global Y coordinate - * @param offsetY - Vertical scroll offset - * @param timestamp - Event timestamp - */ -export const nativeXComponentDispatchMouseWheel: (nativeShellHolderId: number, - xcomponentId: string, - eventType: string, - fingerId: number, - globalX: number, - globalY: number, - offsetY: number, - timestamp: number - ) => void; - - -/** - * Detaches the association between FlutterNapi and the engine. - * This method should only be called when FlutterNapi is already associated with the engine. - * @param nativeShellHolderId - The ID of the native shell holder to destroy - */ -export const nativeDestroy: ( - nativeShellHolderId: number -) => void; - - -/** - * Unregisters a texture from the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture to unregister - */ -export const nativeUnregisterTexture: (nativeShellHolderId: number, textureId: number) => void; - -/** - * Registers a PixelMap as a texture in the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param pixelMap - The PixelMap to register - */ -export const nativeRegisterPixelMap: (nativeShellHolderId: number, textureId: number, pixelMap: PixelMap) => void; - -/** - * Sets the background PixelMap for a texture. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param pixelMap - The PixelMap to use as background - */ -export const nativeSetTextureBackGroundPixelMap: (nativeShellHolderId: number, textureId: number, pixelMap: PixelMap) => void; - -/** - * Sets the background color for a texture. - * @deprecated since 3.7 - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param color - The color value as a number - */ -export const nativeSetTextureBackGroundColor: (nativeShellHolderId: number, textureId: number, color: number) => void; - -/** - * Registers a texture with the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture to register - * @returns The registered texture ID - */ -export const nativeRegisterTexture: (nativeShellHolderId: number, textureId: number) => number; - -/** - * Gets the window ID for a texture. - * @deprecated since 3.22 - * @useinstead nativeGetTextureWindowPtr - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @returns The window ID - */ -export const nativeGetTextureWindowId: (nativeShellHolderId: number, textureId: number) => number; - -/** - * Gets the window pointer for a texture. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @returns The window pointer as a bigint - */ -export const nativeGetTextureWindowPtr: (nativeShellHolderId: number, textureId: number) => bigint; - -/** - * Sets an external native image for a texture. - * @deprecated since 3.22 - * @useinstead nativeSetExternalNativeImagePtr - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param native_image - The native image ID - * @returns The result code - */ -export const nativeSetExternalNativeImage: (nativeShellHolderId: number, textureId: number, native_image: number) => number; - -/** - * Sets an external native image pointer for a texture. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param native_image_ptr - The native image pointer as a bigint - * @returns The result code - */ -export const nativeSetExternalNativeImagePtr: (nativeShellHolderId: number, textureId: number, native_image_ptr: bigint) => number; - -/** - * Resets an external texture. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param need_surfaceId - Whether a surface ID is needed - * @returns The result code - */ -export const nativeResetExternalTexture: (nativeShellHolderId: number, textureId: number, need_surfaceId: boolean) => number; - -/** - * Encodes a string to UTF-8 bytes. - * @param str - The string to encode - * @returns The UTF-8 encoded bytes as a Uint8Array - */ -export const nativeEncodeUtf8: (str: string) => Uint8Array; - -/** - * Decodes UTF-8 bytes to a string. - * @param array - The UTF-8 encoded bytes as a Uint8Array - * @returns The decoded string - */ -export const nativeDecodeUtf8: (array: Uint8Array) => string; - -/** - * Sets the buffer size for a texture. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param width - The buffer width - * @param height - The buffer height - */ -export const nativeSetTextureBufferSize: (nativeShellHolderId: number, textureId: number, width: number, height: number) => void; - -/** - * Notifies the Flutter engine that a texture is being resized. - * @param nativeShellHolderId - The ID of the native shell holder - * @param textureId - The ID of the texture - * @param width - The new width - * @param height - The new height - */ -export const nativeNotifyTextureResizing: (nativeShellHolderId: number, textureId: number, width: number, height: number) => void; - -/** - * Enables or disables frame caching for the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param enable - Whether to enable frame caching - */ -export const nativeEnableFrameCache: (nativeShellHolderId: number, enable: boolean) => void; - -/** - * Looks up callback information for a given handler. - * @param callback - The FlutterCallbackInformation object to populate - * @param handler - The handler number - * @returns The result code - */ -export const nativeLookupCallbackInformation: (callback: FlutterCallbackInformation, handler: number) => number; - -/** - * Checks if a Unicode code point is an emoji. - * @param code - The Unicode code point - * @returns 1 if it is an emoji, 0 otherwise - */ -export const nativeUnicodeIsEmoji: (code: number) => number; - -/** - * Checks if a Unicode code point is an emoji modifier. - * @param code - The Unicode code point - * @returns 1 if it is an emoji modifier, 0 otherwise - */ -export const nativeUnicodeIsEmojiModifier: (code: number) => number; - -/** - * Checks if a Unicode code point is an emoji modifier base. - * @param code - The Unicode code point - * @returns 1 if it is an emoji modifier base, 0 otherwise - */ -export const nativeUnicodeIsEmojiModifierBase: (code: number) => number; - -/** - * Checks if a Unicode code point is a variation selector. - * @param code - The Unicode code point - * @returns 1 if it is a variation selector, 0 otherwise - */ -export const nativeUnicodeIsVariationSelector: (code: number) => number; - -/** - * Checks if a Unicode code point is a regional indicator symbol. - * @param code - The Unicode code point - * @returns 1 if it is a regional indicator symbol, 0 otherwise - */ -export const nativeUnicodeIsRegionalIndicatorSymbol: (code: number) => number; - -/** - * Sets accessibility features in the accessibility channel. - * @param accessibilityFeatureFlags - The accessibility feature flags - * @param responseId - The response ID for the platform message - */ -export const nativeSetAccessibilityFeatures: (accessibilityFeatureFlags: number, responseId: number) => void; - -/** - * Notifies the Flutter engine of an accessibility state change. - * @param nativeShellHolderId - The ID of the native shell holder - * @param state - The new accessibility state - */ -export const nativeAccessibilityStateChange: (nativeShellHolderId: number, state: Boolean) => void; - -/** - * Announces an accessibility message to the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param message - The message to announce - */ -export const nativeAccessibilityAnnounce: (nativeShellHolderId: number, message: string) => void; - -/** - * Handles an accessibility tap event. - * @param nativeShellHolderId - The ID of the native shell holder - * @param nodeId - The ID of the accessibility node that was tapped - */ -export const nativeAccessibilityOnTap: (nativeShellHolderId: number, nodeId: number) => void; - -/** - * Handles an accessibility long press event. - * @param nativeShellHolderId - The ID of the native shell holder - * @param nodeId - The ID of the accessibility node that was long pressed - */ -export const nativeAccessibilityOnLongPress: (nativeShellHolderId: number, nodeId: number) => void; - -/** - * Handles an accessibility tooltip event. - * @param nativeShellHolderId - The ID of the native shell holder - * @param message - The tooltip message - */ -export const nativeAccessibilityOnTooltip: (nativeShellHolderId: number, message: string) => void; - -/** - * Enables or disables semantics for the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param enabled - Whether to enable semantics - */ -export const nativeSetSemanticsEnabled: (nativeShellHolderId: number, enabled: boolean) => void; - -/** - * Sets the font weight scale for the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param fontWeightScale - The font weight scale value - */ -export const nativeSetFontWeightScale: (nativeShellHolderId: number, fontWeightScale: number) => void; - -/** - * Sets the Flutter navigation action state. - * @param nativeShellHolderId - The ID of the native shell holder - * @param isNavigate - Whether navigation is active - */ -export const nativeSetFlutterNavigationAction: (nativeShellHolderId: number, isNavigate: boolean) => void; - -/** - * Enables or disables VSync for the Flutter engine. - * @param nativeShellHolderId - The ID of the native shell holder - * @param isEnable - Whether to enable VSync - */ -export const nativeSetDVsyncSwitch: (nativeShellHolderId: number, isEnable: boolean) => void; - -/** - * Updates the current XComponent ID. - * @param xcomponent_id - The ID of the XComponent - */ -export const nativeUpdateCurrentXComponentId: (xcomponent_id: string) => void; - -/** - * Performs animation voting based on type and velocity. - * @param type - The animation type - * @param velocity - The animation velocity - */ -export const nativeAnimationVoting: (type: number, velocity: number) => void; - -/** - * Performs video voting based on duration and frame count. - * @param seconds - The video duration in seconds - * @param frameCount - The number of frames - */ -export const nativeVideoVoting: (seconds: number, frameCount: number) => void; - -/** - * Prefetches frame configuration. - */ -export const nativePrefetchFramesCfg: () => void; - -/** - * Checks the LTPO (Low Temperature Polycrystalline Oxide) switch state. - * @returns The LTPO switch state value - */ -export const nativeCheckLTPOSwitchState: () => number; - -/** - * Sets QoS (Quality of Service) settings when low memory is detected. - * @param nativeShellHolderId - The ID of the native shell holder - * @param lowMemoryLevel - The low memory level - */ -export const nativeSetQosOnLowMemory: (nativeShellHolderId: number, lowMemoryLevel: number) => void; - -export const nativeSetAnimationStatus: (nativeShellHolderId: number, animationStatus: number) => void; - -export const nativeNotifyPageChanged: (pageName: string, pageNameLen: number, windowID: number) => number; diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/cpp/types/libflutter/oh-package.json5 b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/cpp/types/libflutter/oh-package.json5 deleted file mode 100644 index 68c9ab9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/cpp/types/libflutter/oh-package.json5 +++ /dev/null @@ -1,8 +0,0 @@ - - -{ - "name": "libflutter.so", - "types": "./index.d.ets", - "version": "", - "description": "Please describe the basic information." -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/FlutterInjector.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/FlutterInjector.ets deleted file mode 100644 index 3b2ea50..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/FlutterInjector.ets +++ /dev/null @@ -1,70 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import FlutterNapi from './embedding/engine/FlutterNapi'; -import FlutterLoader from './embedding/engine/loader/FlutterLoader'; - -/** - * Singleton holder for Flutter-related main classes. - * Manages instances of FlutterLoader and FlutterNapi, providing centralized access to these core Flutter components. - */ -export default class FlutterInjector { - private static instance: FlutterInjector; - private flutterLoader: FlutterLoader; - private preloadFlutterNapi: FlutterNapi | null = null; - - /** - * Gets the singleton instance of FlutterInjector. - * @returns The FlutterInjector singleton instance - */ - static getInstance(): FlutterInjector { - if (FlutterInjector.instance == null) { - FlutterInjector.instance = new FlutterInjector(); - } - return FlutterInjector.instance; - } - - /** - * 初始化 - */ - private constructor() { - this.flutterLoader = new FlutterLoader(new FlutterNapi()); - } - - /** - * Gets the FlutterLoader instance. - * @returns The FlutterLoader instance - */ - getFlutterLoader(): FlutterLoader { - return this.flutterLoader; - } - - /** - * Gets a FlutterNapi instance. - * If a preloaded FlutterNapi exists, it is returned and cleared. - * Otherwise, a new FlutterNapi instance is created. - * @returns A FlutterNapi instance - */ - getFlutterNapi(): FlutterNapi { - if (this.preloadFlutterNapi) { - let retFlutterNapi = this.preloadFlutterNapi; - this.preloadFlutterNapi = null; - return retFlutterNapi; - } - return new FlutterNapi(); - } - - /** - * Creates a preloaded FlutterNapi instance. - * This instance will be returned by the next call to getFlutterNapi(). - * If a preloaded instance already exists, it will be replaced with a new one. - * @returns The newly created preloaded FlutterNapi instance - */ - getPreloadFlutterNapi(): FlutterNapi { - this.preloadFlutterNapi = new FlutterNapi(); - return this.preloadFlutterNapi; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/app/FlutterPluginRegistry.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/app/FlutterPluginRegistry.ets deleted file mode 100644 index 38c2873..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/app/FlutterPluginRegistry.ets +++ /dev/null @@ -1,67 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -*/ -import { FlutterView } from '../view/FlutterView'; -import common from '@ohos.app.ability.common'; -import PlatformViewController from '../plugin/platform/PlatformViewsController' - -/** - * Registry for managing Flutter plugins and platform views. - * This class handles the lifecycle of FlutterView, Context, and PlatformViewController, - * providing methods to attach, detach, and manage resources during engine restarts. - */ -export default class FlutterPluginRegistry { - private mPlatformViewsController: PlatformViewController; - private mFlutterView: FlutterView | null = null; - private mContext: common.Context | null = null; - - /** - * Constructs a new FlutterPluginRegistry instance. - * Initializes the platform views controller and sets FlutterView and Context to null. - */ - constructor() { - this.mPlatformViewsController = new PlatformViewController(); - this.mFlutterView = null; - this.mContext = null; - } - - /** - * Attaches a FlutterView and Context to this registry. - * @param flutterView - The FlutterView instance to attach - * @param context - The application context to attach - */ - attach(flutterView: FlutterView, context: common.Context): void { - this.mFlutterView = flutterView; - this.mContext = context; - } - - /** - * Detaches the FlutterView and Context from this registry. - * Cleans up the platform views controller and resets all references. - */ - detach(): void { - this.mPlatformViewsController.detach(); - this.mPlatformViewsController.onDetachedFromNapi(); - this.mFlutterView = null; - this.mContext = null; - } - - /** - * Destroys this registry instance. - * Notifies the platform views controller that it has been detached from NAPI. - */ - destroy(): void { - this.mPlatformViewsController.onDetachedFromNapi(); - } - - /** - * Called before the Flutter engine restarts. - * Notifies the platform views controller to prepare for engine restart. - */ - onPreEngineRestart(): void { - this.mPlatformViewsController.onPreEngineRestart(); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/component/FlutterComponent.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/component/FlutterComponent.ets deleted file mode 100644 index 931486e..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/component/FlutterComponent.ets +++ /dev/null @@ -1,28 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -/** - * Basic Flutter component, not yet fully encapsulated. - * This is a placeholder component that may be used as needed. - */ -@Component -export default struct FlutterComponent { - /** - * Builds the component UI. - * @returns The component tree - */ - build() { - Row() { - Column() { - Text("xxx") - .fontSize(50) - .fontWeight(FontWeight.Bold) - } - .width('100%') - } - .height('100%') - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/component/XComponentStruct.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/component/XComponentStruct.ets deleted file mode 100644 index efe073f..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/component/XComponentStruct.ets +++ /dev/null @@ -1,65 +0,0 @@ -/* -* Copyright (c) 2025 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import Any from '../plugin/common/Any'; -import ApplicationInfoLoader from '../embedding/engine/loader/ApplicationInfoLoader'; - -import { BuilderParams, DVModelParameters } from '../view/DynamicView/dynamicView'; - -/** - * XComponent structure for rendering Flutter content. - * This component creates an XComponent with TEXTURE type to display Flutter views. - * It handles both debug and release modes with different background color configurations. - */ -@Component -struct XComponentStruct { - private context: Any; - private applicationInfo = ApplicationInfoLoader.load(getContext()); - /** Parameters for the DynamicView model, including XComponent ID and other attributes. */ - dvModelParams: DVModelParameters = new DVModelParameters(); - - /** - * Builds the XComponent structure for Flutter rendering. - * Creates an XComponent with TEXTURE type, handling both debug and release modes. - * @returns The XComponent tree - */ - build() { - // todo OS解决默认背景色后可以移除冗余重复代码,仅保留差异的backgroundColor属性条件配置 - if (this.applicationInfo.isDebugMode) { - XComponent({ - id: (this.dvModelParams as Record)["xComponentId"], - type: XComponentType.TEXTURE, - libraryname: 'flutter' - }) - .onLoad((context) => { - this.context = context; - }) - .onDestroy(() => { - }) - .backgroundColor(Color.White) - } else { - XComponent({ - id: (this.dvModelParams as Record)["xComponentId"], - type: XComponentType.TEXTURE, - libraryname: 'flutter' - }) - .onLoad((context) => { - this.context = context; - }) - .onDestroy(() => { - }) - } - } -} - -/** - * Builder function for creating an XComponentStruct component. - * This function is used in DynamicView to build XComponent instances for Flutter rendering. - * @param buildParams - The BuilderParams containing the DVModelParameters for the XComponent - */ -@Builder -export function BuildXComponentStruct(buildParams: BuilderParams) { - XComponentStruct({ dvModelParams: buildParams.params }); -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets deleted file mode 100644 index cfd34fe..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets +++ /dev/null @@ -1,468 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterEngine.java originally written by -* Copyright (C) Copyright 2013 The Flutter Authors. -* -*/ - -import LifecycleChannel from './systemchannels/LifecycleChannel'; -import DartExecutor, { DartEntrypoint } from './dart/DartExecutor'; -import FlutterShellArgs from './FlutterShellArgs'; -import FlutterInjector from '../../FlutterInjector'; -import FlutterLoader from './loader/FlutterLoader'; -import common from '@ohos.app.ability.common'; -import resourceManager from '@ohos.resourceManager'; -import FlutterNapi from './FlutterNapi'; -import NavigationChannel from './systemchannels/NavigationChannel'; -import Log from '../../util/Log'; -import TestChannel from './systemchannels/TestChannel' -import FlutterEngineConnectionRegistry from './FlutterEngineConnectionRegistry'; -import PluginRegistry from './plugins/PluginRegistry'; -import AbilityControlSurface from './plugins/ability/AbilityControlSurface'; -import TextInputChannel from './systemchannels/TextInputChannel'; -import TextInputPlugin from '../../plugin/editing/TextInputPlugin'; -import PlatformChannel from './systemchannels/PlatformChannel'; -import SystemChannel from './systemchannels/SystemChannel'; -import MouseCursorChannel from './systemchannels/MouseCursorChannel'; -import DisplayMetricsChannel from './systemchannels/DisplayMetricsChannel'; -import RestorationChannel from './systemchannels/RestorationChannel'; -import LocalizationChannel from './systemchannels/LocalizationChannel'; -import AccessibilityChannel from './systemchannels/AccessibilityChannel'; -import LocalizationPlugin from '../../plugin/localization/LocalizationPlugin' -import SettingsChannel from './systemchannels/SettingsChannel'; -import SensitiveContentChannel from './systemchannels/SensitiveContentChannel'; -import PlatformViewsController from '../../plugin/platform/PlatformViewsController'; -import { FlutterRenderer } from './renderer/FlutterRenderer'; -import NativeVsyncChannel from './systemchannels/NativeVsyncChannel'; - -const TAG = "FlutterEngine"; - -/** - * A single Flutter execution environment. - * - * The FlutterEngine is the container through which Dart code can be run in an OpenHarmony application. - * - * Dart code in a FlutterEngine can execute in the background, or it can be rendered to the screen by - * using the accompanying FlutterRenderer and Dart code using the Flutter framework on the Dart side. - * Rendering can be started and stopped, thus allowing a FlutterEngine to move from UI interaction - * to data-only processing and then back to UI interaction. - * - * Multiple FlutterEngines may exist, execute Dart code, and render UIs within a single OpenHarmony app. - * For better memory performance characteristics, construct multiple FlutterEngines via FlutterEngineGroup - * rather than via FlutterEngine's constructor directly. - * - * To start running Dart and/or Flutter within this FlutterEngine, get a reference to this engine's - * DartExecutor and then use DartExecutor.executeDartEntrypoint(DartEntrypoint). The - * DartExecutor.executeDartEntrypoint(DartEntrypoint) method must not be invoked twice on the same FlutterEngine. - * - * To start rendering Flutter content to the screen, use getFlutterRenderer() to obtain a FlutterRenderer - * and then attach a RenderSurface. Consider using a FlutterView as a RenderSurface. - * - * Instantiating the first FlutterEngine per process will also load the Flutter engine's native library - * and start the Dart VM. Subsequent FlutterEngines will run on the same VM instance but will have - * their own Dart Isolate when the DartExecutor is run. Each Isolate is a self-contained Dart environment - * and cannot communicate with each other except via Isolate ports. - */ -export default class FlutterEngine implements EngineLifecycleListener { - private engineLifecycleListeners = new Set(); - dartExecutor: DartExecutor; - private flutterLoader: FlutterLoader; - private assetManager: resourceManager.ResourceManager; - //channel定义 - private lifecycleChannel: LifecycleChannel | null = null; - private navigationChannel: NavigationChannel | null = null; - private textInputChannel: TextInputChannel | null = null; - private testChannel: TestChannel | null = null; - private platformChannel: PlatformChannel | null = null; - private sensitiveContentChannel: SensitiveContentChannel | null = null; - private systemChannel: SystemChannel | null = null; - private mouseCursorChannel: MouseCursorChannel | null = null; - private displayMetricsChannel: DisplayMetricsChannel | null = null; - private restorationChannel: RestorationChannel | null = null; - private accessibilityChannel: AccessibilityChannel | null = null; - private localeChannel: LocalizationChannel | null = null; - private flutterNapi: FlutterNapi; - private renderer: FlutterRenderer; - private pluginRegistry: FlutterEngineConnectionRegistry | null = null; - private textInputPlugin: TextInputPlugin | null = null; - private localizationPlugin: LocalizationPlugin | null = null; - private settingsChannel: SettingsChannel | null = null; - private platformViewsController: PlatformViewsController; - private nativeVsyncChannel: NativeVsyncChannel | null = null; - - /** - * Constructs a new FlutterEngine instance. - * Initializes DartExecutor, channels, plugins, FlutterLoader, FlutterNapi, and lifecycle listeners. - * @param context - The application context - * @param flutterLoader - Optional FlutterLoader instance, will be created if null - * @param flutterNapi - Optional FlutterNapi instance, will be created if null - * @param platformViewsController - Optional PlatformViewsController, will be created if null - */ - constructor(context: common.Context, flutterLoader: FlutterLoader | null, flutterNapi: FlutterNapi | null, - platformViewsController: PlatformViewsController | null) { - const injector: FlutterInjector = FlutterInjector.getInstance(); - if (flutterNapi == null) { - flutterNapi = FlutterInjector.getInstance().getFlutterNapi(); - } - this.flutterNapi = flutterNapi; - this.assetManager = context.resourceManager; - - this.dartExecutor = new DartExecutor(this.flutterNapi, this.assetManager); - this.dartExecutor.onAttachedToNAPI(); - - if (flutterLoader == null) { - flutterLoader = injector.getFlutterLoader(); - } - this.flutterLoader = flutterLoader; - - this.renderer = new FlutterRenderer(this.flutterNapi); - - if (platformViewsController == null) { - platformViewsController = new PlatformViewsController(); - } - this.platformViewsController = platformViewsController; - this.platformViewsController.attach(context, this.renderer, this.dartExecutor); - } - - /** - * Initializes the Flutter engine. - * Sets up all channels, plugins, and attaches to the native engine. - * @param context - The application context - * @param dartVmArgs - Optional Dart VM arguments - * @param waitForRestorationData - Whether to wait for restoration data - */ - init(context: common.Context, dartVmArgs: Array | null, waitForRestorationData: boolean) { - if (!this.flutterNapi.isAttached()) { - this.flutterLoader.startInitialization(context) - this.flutterLoader.ensureInitializationComplete(dartVmArgs); - } - //channel初始化 - this.lifecycleChannel = new LifecycleChannel(this.dartExecutor); - this.navigationChannel = new NavigationChannel(this.dartExecutor, context); - this.textInputChannel = new TextInputChannel(this.dartExecutor); - this.testChannel = new TestChannel(this.dartExecutor); - this.platformChannel = new PlatformChannel(this.dartExecutor, this.flutterNapi); - this.sensitiveContentChannel = new SensitiveContentChannel(this.dartExecutor); - this.systemChannel = new SystemChannel(this.dartExecutor); - this.mouseCursorChannel = new MouseCursorChannel(this.dartExecutor); - this.displayMetricsChannel = new DisplayMetricsChannel(this.dartExecutor, context); - this.restorationChannel = new RestorationChannel(this.dartExecutor, waitForRestorationData); - this.settingsChannel = new SettingsChannel(this.dartExecutor); - this.localeChannel = new LocalizationChannel(this.dartExecutor); - this.accessibilityChannel = new AccessibilityChannel(this.dartExecutor, this.flutterNapi); - this.flutterNapi.addEngineLifecycleListener(this); - this.localizationPlugin = new LocalizationPlugin(context, this.localeChannel); - this.nativeVsyncChannel = new NativeVsyncChannel(this.dartExecutor, this.flutterNapi); - - // It should typically be a fresh, unattached NAPI. But on a spawned engine, the NAPI instance - // is already attached to a native shell. In that case, the Java FlutterEngine is created around - // an existing shell. - if (!this.flutterNapi.isAttached()) { - this.attachToNapi(); - } - this.flutterNapi.setLocalizationPlugin(this.localizationPlugin); - - this.pluginRegistry = - new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, this.flutterLoader); - this.localizationPlugin.sendLocaleToFlutter(); - Log.d(TAG, "Call init finished.") - } - - private attachToNapi(): void { - Log.d(TAG, "Attaching to NAPI."); - this.flutterNapi.attachToNative(); - if (!this.isAttachedToNapi()) { - throw new Error("FlutterEngine failed to attach to its native Object reference."); - } - } - - /** - * Spawns a new Flutter engine from this engine. - * The spawned engine shares resources with the parent engine. - * @param context - The application context - * @param dartEntrypoint - The Dart entrypoint configuration - * @param initialRoute - The initial route for navigation - * @param dartEntrypointArgs - Arguments for the Dart entrypoint - * @param platformViewsController - The platform views controller - * @param waitForRestorationData - Whether to wait for restoration data - * @returns The spawned FlutterEngine instance - * @throws Error if this engine is not fully constructed - */ - spawn(context: common.Context, - dartEntrypoint: DartEntrypoint, - initialRoute: string, - dartEntrypointArgs: Array, - platformViewsController: PlatformViewsController, - waitForRestorationData: boolean) { - if (!this.isAttachedToNapi()) { - throw new Error( - "Spawn can only be called on a fully constructed FlutterEngine"); - } - - const newFlutterNapi = - this.flutterNapi.spawn( - dartEntrypoint.dartEntrypointFunctionName, - dartEntrypoint.dartEntrypointLibrary, - initialRoute, - dartEntrypointArgs); - const flutterEngine = new FlutterEngine( - context, - null, - newFlutterNapi, - platformViewsController - ); - flutterEngine.init(context, null, waitForRestorationData) - return flutterEngine - } - - private isAttachedToNapi(): boolean { - return this.flutterNapi.isAttached(); - } - - /** - * Processes any pending messages from the native side. - */ - processPendingMessages() { - if (this.flutterNapi.isAttached()) { - this.flutterNapi.processPendingMessages(); - } - } - - /** - * Gets the lifecycle channel for managing application lifecycle events. - * @returns The LifecycleChannel instance, or null if not initialized - */ - getLifecycleChannel(): LifecycleChannel | null { - return this.lifecycleChannel; - } - - /** - * Gets the navigation channel for managing navigation events. - * @returns The NavigationChannel instance, or null if not initialized - */ - getNavigationChannel(): NavigationChannel | null { - return this.navigationChannel; - } - - /** - * Gets the text input channel for managing text input events. - * @returns The TextInputChannel instance, or null if not initialized - */ - getTextInputChannel(): TextInputChannel | null { - return this.textInputChannel; - } - - /** - * Gets the platform channel for platform-specific communication. - * @returns The PlatformChannel instance, or null if not initialized - */ - getPlatformChannel(): PlatformChannel | null { - return this.platformChannel; - } - - /** - * Gets the system channel for system-level communication. - * @returns The SystemChannel instance, or null if not initialized - */ - getSystemChannel(): SystemChannel | null { - return this.systemChannel; - } - - /** - * Gets the localization channel for managing locale information. - * @returns The LocalizationChannel instance, or null if not initialized - */ - getLocaleChannel(): LocalizationChannel | null { - return this.localeChannel; - } - - /** - * Gets the mouse cursor channel for managing cursor changes. - * @returns The MouseCursorChannel instance, or null if not initialized - */ - getMouseCursorChannel(): MouseCursorChannel | null { - return this.mouseCursorChannel; - } - - getDisplayMetricsChannel(): DisplayMetricsChannel | null { - return this.displayMetricsChannel; - } - - /** - * Gets the FlutterNapi instance for native communication. - * @returns The FlutterNapi instance - */ - getFlutterNapi(): FlutterNapi { - return this.flutterNapi; - } - - /** - * Gets the FlutterRenderer instance for rendering operations. - * @returns The FlutterRenderer instance - */ - getFlutterRenderer(): FlutterRenderer { - return this.renderer; - } - - /** - * Gets the DartExecutor instance for executing Dart code. - * @returns The DartExecutor instance - */ - getDartExecutor(): DartExecutor { - return this.dartExecutor - } - - getSensitiveContentChannel():SensitiveContentChannel |null { - return this.sensitiveContentChannel - } - - /** - * Gets the plugin registry for managing Flutter plugins. - * @returns The PluginRegistry instance, or null if not initialized - */ - getPlugins(): PluginRegistry | null { - return this.pluginRegistry; - } - - /** - * Gets the ability control surface for managing ability-related operations. - * @returns The AbilityControlSurface instance, or null if not initialized - */ - getAbilityControlSurface(): AbilityControlSurface | null { - return this.pluginRegistry; - } - - /** - * Gets the settings channel for managing application settings. - * @returns The SettingsChannel instance, or null if not initialized - */ - getSettingsChannel() { - return this.settingsChannel; - } - - /** - * Gets the FlutterLoader instance for loading Flutter assets. - * @returns The FlutterLoader instance - */ - getFlutterLoader() { - return this.flutterLoader; - } - - /** - * Called before the engine restarts. - * Notifies all registered lifecycle listeners. - */ - onPreEngineRestart(): void { - this.engineLifecycleListeners.forEach(listener => listener.onPreEngineRestart()) - } - - /** - * Called when the engine is about to be destroyed. - */ - onEngineWillDestroy(): void { - - } - - /** - * Adds a lifecycle listener to be notified of engine lifecycle events. - * @param listener - The EngineLifecycleListener to add - */ - addEngineLifecycleListener(listener: EngineLifecycleListener): void { - this.engineLifecycleListeners.add(listener); - } - - /** - * Removes a lifecycle listener from the list of registered listeners. - * @param listener - The EngineLifecycleListener to remove - */ - removeEngineLifecycleListener(listener: EngineLifecycleListener): void { - this.engineLifecycleListeners.delete(listener); - } - - /** - * Destroys the Flutter engine and releases all resources. - * Notifies listeners, detaches from ability, and cleans up all components. - */ - destroy(): void { - Log.d(TAG, "Destroying."); - this.engineLifecycleListeners.forEach(listener => listener.onEngineWillDestroy()) - this.flutterNapi.removeEngineLifecycleListener(this); - this.pluginRegistry?.detachFromAbility(); - this.platformViewsController?.onDetachedFromNapi(); - this.pluginRegistry?.destroy(); - this.dartExecutor.onDetachedFromNAPI(); - this.flutterNapi.detachFromNativeAndReleaseResources(); - } - - /** - * Gets the restoration channel for managing state restoration. - * @returns The RestorationChannel instance, or null if not initialized - */ - getRestorationChannel(): RestorationChannel | null { - return this.restorationChannel; - } - - /** - * Gets the accessibility channel for managing accessibility features. - * @returns The AccessibilityChannel instance, or null if not initialized - */ - getAccessibilityChannel(): AccessibilityChannel | null { - return this.accessibilityChannel; - } - - /** - * Gets the localization plugin for managing locale information. - * @returns The LocalizationPlugin instance, or null if not initialized - */ - getLocalizationPlugin(): LocalizationPlugin | null { - return this.localizationPlugin; - } - - /** - * Gets the system languages from the native side. - */ - getSystemLanguages(): void { - return this.flutterNapi.getSystemLanguages(); - } - - /** - * Gets the platform views controller for managing platform views. - * @returns The PlatformViewsController instance, or null if not initialized - */ - getPlatformViewsController(): PlatformViewsController | null { - return this.platformViewsController; - } - - /** - * Gets the native VSync channel for managing vertical synchronization. - * @returns The NativeVsyncChannel instance, or null if not initialized - */ - getNativeVsyncChannel(): NativeVsyncChannel | null { - return this.nativeVsyncChannel; - } - - /** - * Prefetches frame configuration for improved performance. - */ - async prefetchFramesCfg(): Promise { - FlutterNapi.prefetchFramesCfg(); - } -} - -/** - * Interface for listening to Flutter engine lifecycle events. - */ -export interface EngineLifecycleListener { - /** - * Called before the engine restarts. - */ - onPreEngineRestart(): void; - - /** - * Called when the engine is about to be destroyed. - */ - onEngineWillDestroy(): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineCache.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineCache.ets deleted file mode 100644 index f530204..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineCache.ets +++ /dev/null @@ -1,84 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterEngineCache.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import FlutterEngine from "./FlutterEngine" - -/** - * Static singleton cache that holds FlutterEngine instances identified by Strings. - * - * The ID of a given FlutterEngine can be whatever String is desired. - * - * FlutterEngineCache is useful for storing pre-warmed FlutterEngine instances. FlutterAbility - * and FlutterEntry can use cached FlutterEngine instances by implementing getCachedEngineId() - * to return a cached engine ID. The FlutterAbilityAndEntryDelegate will then retrieve the - * engine from this cache. See FlutterAbility.getCachedEngineId() and FlutterEntry.getCachedEngineId() - * for related APIs. - */ -export default class FlutterEngineCache { - private static instance: FlutterEngineCache; - private cachedEngines: Map = new Map(); - - /** - * Gets the singleton instance of FlutterEngineCache. - * @returns The FlutterEngineCache instance - */ - static getInstance(): FlutterEngineCache { - if (FlutterEngineCache.instance == null) { - FlutterEngineCache.instance = new FlutterEngineCache(); - } - return FlutterEngineCache.instance; - } - - /** - * Checks if an engine with the given ID exists in the cache. - * @param engineId - The ID of the engine to check - * @returns true if the engine exists, false otherwise - */ - contains(engineId: String): boolean { - return this.cachedEngines.has(engineId); - } - - /** - * Gets an engine from the cache by ID. - * @param engineId - The ID of the engine to retrieve - * @returns The FlutterEngine instance, or null if not found - */ - get(engineId: String): FlutterEngine | null { - return this.cachedEngines.get(engineId) || null; - } - - /** - * Puts an engine into the cache or removes it if null is provided. - * @param engineId - The ID of the engine - * @param engine - The FlutterEngine instance to cache, or null to remove - */ - put(engineId: String, engine: FlutterEngine | null): void { - if (engine != null) { - this.cachedEngines.set(engineId, engine); - } else { - this.cachedEngines.delete(engineId); - } - } - - /** - * Removes an engine from the cache by ID. - * @param engineId - The ID of the engine to remove - */ - remove(engineId: String): void { - this.put(engineId, null); - } - - /** - * Clears all engines from the cache. - */ - clear(): void { - this.cachedEngines.clear(); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ets deleted file mode 100644 index 74b5f20..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ets +++ /dev/null @@ -1,426 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterEngineConnectionRegistry.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import PluginRegistry from './plugins/PluginRegistry'; -import { FlutterAssets, FlutterPlugin, FlutterPluginBinding } from './plugins/FlutterPlugin'; -import FlutterEngine from './FlutterEngine'; -import AbilityAware from './plugins/ability/AbilityAware'; -import UIAbility from '@ohos.app.ability.UIAbility'; -import { - AbilityPluginBinding, - WindowFocusChangedListener, - OnSaveStateListener, - NewWantListener -} from './plugins/ability/AbilityPluginBinding'; -import HashSet from '@ohos.util.HashSet'; -import Want from '@ohos.app.ability.Want'; -import AbilityConstant from '@ohos.app.ability.AbilityConstant'; -import common from '@ohos.app.ability.common'; -import FlutterLoader from './loader/FlutterLoader'; -import Log from '../../util/Log'; -import ToolUtils from '../../util/ToolUtils'; -import AbilityControlSurface from './plugins/ability/AbilityControlSurface'; -import ExclusiveAppComponent from '../ohos/ExclusiveAppComponent'; -import FlutterEngineGroup from './FlutterEngineGroup'; -import Any from '../../plugin/common/Any'; - -const TAG = "FlutterEngineCxnRegistry"; - -/** - * Registry for managing Flutter plugins and their connections to the engine and ability. - * This class implements both PluginRegistry and AbilityControlSurface interfaces, - * providing a unified way to manage plugin lifecycle and ability-related operations. - */ -export default class FlutterEngineConnectionRegistry implements PluginRegistry, AbilityControlSurface { - private plugins = new Map(); - private defaultPlugin: FlutterPlugin = new EmptyPlugin(); - private flutterEngine: FlutterEngine; - private pluginBinding: FlutterPluginBinding; - private abilityAwarePlugins = new Map(); - private exclusiveAbility: ExclusiveAppComponent | null = null; - private abilityPluginBinding: FlutterEngineAbilityPluginBinding | null = null; - - /** - * Constructs a new FlutterEngineConnectionRegistry instance. - * @param appContext - The application context - * @param flutterEngine - The FlutterEngine instance this registry is associated with - * @param flutterLoader - The FlutterLoader instance for asset management - */ - constructor(appContext: common.Context, flutterEngine: FlutterEngine, flutterLoader: FlutterLoader) { - this.flutterEngine = flutterEngine; - this.pluginBinding = new FlutterPluginBinding(appContext, flutterEngine, flutterEngine.getDartExecutor(), - new DefaultFlutterAssets(flutterLoader), flutterEngine.getFlutterRenderer(), - flutterEngine.getPlatformViewsController()?.getRegistry()); - } - - /** - * Adds a plugin to this registry. - * @param plugin - The FlutterPlugin instance to add - */ - add(plugin: FlutterPlugin): void { - try { - if (this.has(plugin.getUniqueClassName())) { - Log.w( - TAG, - "Attempted to register plugin (" - + plugin - + ") but it was " - + "already registered with this FlutterEngine (" - + this.flutterEngine - + ")."); - return; - } - - Log.w(TAG, "Adding plugin: " + plugin.getUniqueClassName()); - // Add the plugin to our generic set of plugins and notify the plugin - // that is has been attached to an engine. - this.plugins.set(plugin.getUniqueClassName(), plugin); - plugin.onAttachedToEngine(this.pluginBinding); - - // For AbilityAware plugins, add the plugin to our set of AbilityAware - // plugins, and if this engine is currently attached to an Ability, - // notify the AbilityAware plugin that it is now attached to an Ability. - if (ToolUtils.implementsInterface(plugin, "onAttachedToAbility")) { - const abilityAware: Any = plugin; - this.abilityAwarePlugins.set(plugin.getUniqueClassName(), abilityAware); - if (this.isAttachedToAbility()) { - abilityAware.onAttachedToAbility(this.abilityPluginBinding); - } - } - } finally { - - } - } - - /** - * Adds multiple plugins to this registry. - * @param plugins - Set of FlutterPlugin instances to add - */ - addList(plugins: Set): void { - plugins.forEach(plugin => this.add(plugin)) - } - - /** - * Checks if a plugin with the given class name is registered. - * @param pluginClassName - The class name of the plugin to check - * @returns true if the plugin is registered, false otherwise - */ - has(pluginClassName: string): boolean { - return this.plugins.has(pluginClassName); - } - - /** - * Gets a plugin by its class name. - * @param pluginClassName - The class name of the plugin to retrieve - * @returns The FlutterPlugin instance, or a default empty plugin if not found - */ - get(pluginClassName: string): FlutterPlugin { - return this.plugins.get(pluginClassName) ?? this.defaultPlugin; - } - - /** - * Removes a plugin from this registry. - * @param pluginClassName - The class name of the plugin to remove - */ - remove(pluginClassName: string): void { - const plugin = this.plugins.get(pluginClassName); - if (plugin == null) { - return; - } - if (ToolUtils.implementsInterface(plugin, "onAttachedToAbility")) { - if (this.isAttachedToAbility()) { - const abilityAware: Any = plugin; - abilityAware.onDetachedFromAbility(); - } - this.abilityAwarePlugins.delete(pluginClassName); - } - // Notify the plugin that is now detached from this engine. Then remove - // it from our set of generic plugins. - plugin.onDetachedFromEngine(this.pluginBinding); - this.plugins.delete(pluginClassName) - } - - /** - * Removes multiple plugins from this registry. - * @param pluginClassNames - Set of plugin class names to remove - */ - removeList(pluginClassNames: Set): void { - pluginClassNames.forEach(plugin => this.remove(plugin)) - } - - /** - * Removes all plugins from this registry. - */ - removeAll(): void { - this.removeList(new Set(this.plugins.keys())); - this.plugins.clear(); - } - - private isAttachedToAbility(): boolean { - return this.exclusiveAbility != null; - } - - /** - * Attaches this registry to an ability. - * @param exclusiveAbility - The exclusive app component (UIAbility) to attach to - */ - attachToAbility(exclusiveAbility: ExclusiveAppComponent): void { - if (this.exclusiveAbility != null) { - this.exclusiveAbility.detachFromFlutterEngine(); - } - // If we were already attached to an app component, detach from it. - this.detachFromAppComponent(); - this.exclusiveAbility = exclusiveAbility; - this.attachToAbilityInternal(exclusiveAbility.getAppComponent(),); - } - - /** - * Detaches this registry from the current ability. - */ - detachFromAbility(): void { - if (this.isAttachedToAbility()) { - try { - this.abilityAwarePlugins.forEach(abilityAware => abilityAware.onDetachedFromAbility()) - } catch (e) { - Log.e(TAG, "abilityAwarePlugins DetachedFromAbility failed, msg:" + e); - } - this.detachFromAbilityInternal(); - } else { - Log.e(TAG, "Attempted to detach plugins from an Ability when no Ability was attached."); - } - } - - /** - * Handles a new Want event from the ability. - * @param want - The Want object containing the intent - * @param launchParams - The launch parameters - */ - onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { - this.abilityPluginBinding?.onNewWant(want, launchParams); - } - - /** - * Handles window focus change events from the ability. - * @param hasFocus - Whether the window has focus - */ - onWindowFocusChanged(hasFocus: boolean): void { - this.abilityPluginBinding?.onWindowFocusChanged(hasFocus); - } - - /** - * Handles save state requests from the ability. - * @param reason - The reason for saving state - * @param wantParam - Parameters to save - * @returns The result of the save state operation - */ - onSaveState(reason: AbilityConstant.StateType, wantParam: Record): AbilityConstant.OnSaveResult { - return this.abilityPluginBinding?.onSaveState(reason, wantParam) ?? AbilityConstant.OnSaveResult.ALL_REJECT; - } - - private detachFromAppComponent(): void { - if (this.isAttachedToAbility()) { - this.detachFromAbility(); - } - } - - private attachToAbilityInternal(ability: UIAbility): void { - this.abilityPluginBinding = new FlutterEngineAbilityPluginBinding(ability); - // Notify all AbilityAware plugins that they are now attached to a new Ability. - this.abilityAwarePlugins.forEach(abilityAware => abilityAware.onAttachedToAbility(this.abilityPluginBinding!)); - } - - private detachFromAbilityInternal(): void { - this.exclusiveAbility = null; - this.abilityPluginBinding = null; - } - - /** - * Destroys this registry and removes all plugins. - */ - destroy(): void { - this.detachFromAppComponent(); - // Remove all registered plugins. - this.removeAll(); - } -} - -/** - * Implementation of AbilityPluginBinding for FlutterEngine. - * Manages ability-related listeners and events for plugins. - */ -class FlutterEngineAbilityPluginBinding implements AbilityPluginBinding { - private ability: UIAbility; - private onNewWantListeners = new HashSet(); - private onWindowFocusChangedListeners = new HashSet(); - private onSaveStateListeners = new HashSet(); - - /** - * Constructs a new FlutterEngineAbilityPluginBinding instance. - * @param ability - The UIAbility instance this binding is associated with - */ - constructor(ability: UIAbility) { - this.ability = ability; - - } - - /** - * Gets the UIAbility instance. - * @returns The UIAbility instance - */ - getAbility(): UIAbility { - return this.ability; - } - - /** - * Adds a listener for new Want events. - * @param listener - The NewWantListener to add - */ - addOnNewWantListener(listener: NewWantListener): void { - this.onNewWantListeners.add(listener) - } - - /** - * Removes a listener for new Want events. - * @param listener - The NewWantListener to remove - */ - removeOnNewWantListener(listener: NewWantListener): void { - this.onNewWantListeners.remove(listener) - } - - /** - * Adds a listener for window focus change events. - * @param listener - The WindowFocusChangedListener to add - */ - addOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void { - this.onWindowFocusChangedListeners.add(listener) - } - - /** - * Removes a listener for window focus change events. - * @param listener - The WindowFocusChangedListener to remove - */ - removeOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void { - this.onWindowFocusChangedListeners.remove(listener) - } - - /** - * Adds a listener for save state events. - * @param listener - The OnSaveStateListener to add - */ - addOnSaveStateListener(listener: OnSaveStateListener) { - this.onSaveStateListeners.add(listener) - } - - /** - * Removes a listener for save state events. - * @param listener - The OnSaveStateListener to remove - */ - removeOnSaveStateListener(listener: OnSaveStateListener) { - this.onSaveStateListeners.remove(listener) - } - - /** - * Notifies all registered listeners of a new Want event. - * @param want - The Want object - * @param launchParams - The launch parameters - */ - onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { - this.onNewWantListeners.forEach((listener, key) => { - listener?.onNewWant(want, launchParams) - }); - } - - /** - * Notifies all registered listeners of a window focus change. - * @param hasFocus - Whether the window has focus - */ - onWindowFocusChanged(hasFocus: boolean): void { - this.onWindowFocusChangedListeners.forEach((listener, key) => { - listener?.onWindowFocusChanged(hasFocus) - }); - } - - /** - * Notifies all registered listeners of a save state request. - * @param reason - The reason for saving state - * @param wantParam - Parameters to save - * @returns The result of the save state operation - */ - onSaveState(reason: AbilityConstant.StateType, wantParam: Record): AbilityConstant.OnSaveResult { - this.onSaveStateListeners.forEach((listener, key) => { - listener?.onSaveState(reason, wantParam) - }); - return AbilityConstant.OnSaveResult.ALL_AGREE; - } -} - -/** - * Default implementation of FlutterAssets using FlutterLoader. - */ -class DefaultFlutterAssets implements FlutterAssets { - private flutterLoader: FlutterLoader; - - /** - * Constructs a new DefaultFlutterAssets instance. - * @param flutterLoader - The FlutterLoader instance for asset lookup - */ - constructor(flutterLoader: FlutterLoader) { - this.flutterLoader = flutterLoader; - } - - /** - * Gets the file path for an asset by name. - * @param assetFileName - The name of the asset file - * @param packageName - Optional package name - * @returns The file path for the asset - */ - getAssetFilePathByName(assetFileName: string, packageName?: string): string { - return this.flutterLoader.getLookupKeyForAsset(assetFileName, packageName); - } - - /** - * Gets the file path for an asset by subpath. - * @param assetSubpath - The subpath of the asset - * @param packageName - Optional package name - * @returns The file path for the asset - */ - getAssetFilePathBySubpath(assetSubpath: string, packageName?: string) { - return this.flutterLoader.getLookupKeyForAsset(assetSubpath, packageName); - } -} - -/** - * Empty plugin implementation used as a default when a plugin is not found. - */ -class EmptyPlugin implements FlutterPlugin { - /** - * Gets the unique class name of this plugin. - * @returns An empty string - */ - getUniqueClassName(): string { - return ''; - } - - /** - * Called when this plugin is attached to an engine. - * @param binding - The FlutterPluginBinding instance - */ - onAttachedToEngine(binding: FlutterPluginBinding) { - - } - - /** - * Called when this plugin is detached from an engine. - * @param binding - The FlutterPluginBinding instance - */ - onDetachedFromEngine(binding: FlutterPluginBinding) { - - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineGroup.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineGroup.ets deleted file mode 100644 index 6db5011..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineGroup.ets +++ /dev/null @@ -1,292 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterEngineGroup.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import FlutterEngine, { EngineLifecycleListener } from "./FlutterEngine" -import common from '@ohos.app.ability.common' -import display from '@ohos.display'; -import FlutterLoader from './loader/FlutterLoader' -import FlutterInjector from '../../FlutterInjector' -import { DartEntrypoint } from './dart/DartExecutor' -import PlatformViewsController from '../../plugin/platform/PlatformViewsController' -import ArrayList from '@ohos.util.ArrayList' -import Log from '../../util/Log'; -import FlutterManager from '../ohos/FlutterManager'; - -const TAG = "FlutterEngineGroup" - -/** - * Represents a collection of FlutterEngines who share resources to allow them to be created - * faster and with less memory than calling the FlutterEngine's constructor multiple times. - * - * When creating or recreating the first FlutterEngine in the FlutterEngineGroup, the behavior - * is the same as creating a FlutterEngine via its constructor. When subsequent FlutterEngines - * are created, resources from an existing living FlutterEngine is re-used. - * - * The shared resources are kept until the last surviving FlutterEngine is destroyed. - * - * Deleting a FlutterEngineGroup doesn't invalidate its existing FlutterEngines, but it eliminates - * the possibility to create more FlutterEngines in that group. - */ -export default class FlutterEngineGroup { - private activeEngines: ArrayList = new ArrayList(); - - /** - * Constructs a new FlutterEngineGroup instance. - */ - constructor() { - - } - - /** - * Checks and initializes the FlutterLoader if not already initialized. - * @param context - The application context - * @param args - Command-line arguments for Dart VM initialization - */ - checkLoader(context: common.Context, args: Array) { - let loader: FlutterLoader = FlutterInjector.getInstance().getFlutterLoader(); - if (!loader.initialized) { - loader.startInitialization(context); - loader.ensureInitializationComplete(args); - } - } - - /** - * Creates and runs a Flutter engine based on the provided options. - * If no engines exist, creates a new engine. Otherwise, spawns from the first engine. - * @param options - Configuration options for engine creation - * @returns The created or spawned FlutterEngine instance - */ - createAndRunEngineByOptions(options: Options) { - let engine: FlutterEngine | null = null; - let context: common.Context = options.getContext(); - let dartEntrypoint: DartEntrypoint | null = options.getDartEntrypoint(); - let initialRoute: string = options.getInitialRoute(); - let dartEntrypointArgs: Array = options.getDartEntrypointArgs(); - let platformViewsController: PlatformViewsController | null = options.getPlatformViewsController(); - let waitForRestorationData: boolean = options.getWaitForRestorationData(); - - if (dartEntrypoint == null) { - dartEntrypoint = DartEntrypoint.createDefault(); - } - - if (platformViewsController == null) { - platformViewsController = new PlatformViewsController(); - } - - Log.i(TAG, "shellHolder, this.activeEngines.length=" + this.activeEngines.length) - if (this.activeEngines.length == 0) { - engine = this.createEngine(context, platformViewsController); - engine.init(context, null, // String[]. The Dart VM has already started, this arguments will have no effect. - waitForRestorationData) - if (initialRoute != null) { - engine.getNavigationChannel()?.setInitialRoute(initialRoute); - } - engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint, dartEntrypointArgs); - engine.prefetchFramesCfg(); - } else { - engine = this.activeEngines[0] - .spawn( - context, - dartEntrypoint, - initialRoute, - dartEntrypointArgs, - platformViewsController, - waitForRestorationData); - } - this.activeEngines.add(engine); - - const engineToCleanUpOnDestroy = engine; - let listener: EngineLifecycleListener = new EngineLifecycleListenerImpl( - platformViewsController, - this.activeEngines, - engineToCleanUpOnDestroy); - engine?.addEngineLifecycleListener(listener); - return engine; - } - - /** - * Creates a new FlutterEngine instance. - * @param context - The application context - * @param platformViewsController - The platform views controller for the engine - * @returns A new FlutterEngine instance - */ - createEngine(context: common.Context, platformViewsController: PlatformViewsController): FlutterEngine { - return new FlutterEngine(context, null, null, platformViewsController); - } - - /** - * Gets the default (first) engine in this group. - * @returns The default FlutterEngine, or null if no engines exist - */ - getDefaultEngine(): FlutterEngine | null { - let engine: FlutterEngine | null = null; - if (this.activeEngines.length != 0) { - engine = this.activeEngines[0]; - } - return engine; - } -} - -/** - * Implementation of EngineLifecycleListener for managing engine lifecycle in a group. - */ -class EngineLifecycleListenerImpl implements EngineLifecycleListener { - private platformViewsController: PlatformViewsController; - private activeEngines: ArrayList = new ArrayList(); - private engine: FlutterEngine | null; - - /** - * Constructs a new EngineLifecycleListenerImpl instance. - * @param platformViewsController - The platform views controller - * @param activeEngines - List of active engines in the group - * @param engine - The engine this listener is associated with - */ - constructor( - platformViewsController: PlatformViewsController, - activeEngines: ArrayList, - engine: FlutterEngine | null) { - this.platformViewsController = platformViewsController; - this.activeEngines = activeEngines; - this.engine = engine; - } - - /** - * Called before the engine restarts. - */ - onPreEngineRestart(): void { - this.platformViewsController.onPreEngineRestart(); - } - - /** - * Called when the engine is about to be destroyed. - * Removes the engine from the active engines list. - */ - onEngineWillDestroy(): void { - this.activeEngines.remove(this.engine); - } -} - -/** - * Options that control how a FlutterEngine should be created.. - */ -export class Options { - private context: common.Context; - private dartEntrypoint: DartEntrypoint | null = null; - private initialRoute: string = ''; - private dartEntrypointArgs: Array = []; - private platformViewsController: PlatformViewsController | null = null; - private waitForRestorationData: boolean = false; - - /** - * Constructs a new Options instance. - * @param context - The application context - */ - constructor(context: common.Context) { - this.context = context; - } - - /** - * Gets the application context. - * @returns The context - */ - getContext(): common.Context { - return this.context; - } - - /** - * Gets the Dart entrypoint configuration. - * @returns The DartEntrypoint, or null if not set - */ - getDartEntrypoint(): DartEntrypoint | null { - return this.dartEntrypoint; - } - - /** - * Gets the initial route for navigation. - * @returns The initial route string - */ - getInitialRoute(): string { - return this.initialRoute; - } - - /** - * Gets the Dart entrypoint arguments. - * @returns Array of entrypoint arguments - */ - getDartEntrypointArgs(): Array { - return this.dartEntrypointArgs; - } - - /** - * Gets whether to wait for restoration data. - * @returns true if waiting for restoration data, false otherwise - */ - getWaitForRestorationData(): boolean { - return this.waitForRestorationData; - } - - /** - * Gets the platform views controller. - * @returns The PlatformViewsController, or null if not set - */ - getPlatformViewsController(): PlatformViewsController | null { - return this.platformViewsController; - } - - /** - * Sets the Dart entrypoint configuration. - * @param dartEntrypoint - The DartEntrypoint to set - * @returns This Options instance for method chaining - */ - setDartEntrypoint(dartEntrypoint: DartEntrypoint): Options { - this.dartEntrypoint = dartEntrypoint; - return this; - } - - /** - * Sets the initial route for navigation. - * @param initialRoute - The initial route string - * @returns This Options instance for method chaining - */ - setInitialRoute(initialRoute: string): Options { - this.initialRoute = initialRoute; - return this; - } - - /** - * Sets the Dart entrypoint arguments. - * @param dartEntrypointArgs - Array of entrypoint arguments - * @returns This Options instance for method chaining - */ - setDartEntrypointArgs(dartEntrypointArgs: Array): Options { - this.dartEntrypointArgs = dartEntrypointArgs; - return this; - } - - /** - * Sets whether to wait for restoration data. - * @param waitForRestorationData - Whether to wait for restoration data - * @returns This Options instance for method chaining - */ - setWaitForRestorationData(waitForRestorationData: boolean): Options { - this.waitForRestorationData = waitForRestorationData; - return this; - } - - /** - * Sets the platform views controller. - * @param platformViewsController - The PlatformViewsController to set - * @returns This Options instance for method chaining - */ - setPlatformViewsController(platformViewsController: PlatformViewsController): Options { - this.platformViewsController = platformViewsController; - return this; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineGroupCache.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineGroupCache.ets deleted file mode 100644 index 8d1d7d3..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngineGroupCache.ets +++ /dev/null @@ -1,61 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import FlutterEngineGroup from './FlutterEngineGroup'; - -/** - * Static singleton cache that holds FlutterEngineGroup instances identified by Strings. - * - * The ID of a given FlutterEngineGroup can be whatever String is desired. - * - * FlutterEngineGroupCache is useful for storing pre-warmed FlutterEngineGroup instances. FlutterAbility - * and FlutterEntry can use cached FlutterEngineGroup instances by implementing getCachedEngineGroupId() - * to return a cached engine group ID. The FlutterAbilityAndEntryDelegate will then retrieve the - * engine group from this cache and create new engines within that group. See FlutterAbility.getCachedEngineGroupId() - * and FlutterEntry.getCachedEngineGroupId() for related APIs. - */ -export default class FlutterEngineGroupCache { - static readonly instance = new FlutterEngineGroupCache(); - private cachedEngineGroups = new Map(); - - /** - * Checks if an engine group with the given ID exists in the cache. - * @param engineGroupId - The ID of the engine group to check - * @returns true if the engine group exists, false otherwise - */ - contains(engineGroupId: string): boolean { - return this.cachedEngineGroups.has(engineGroupId); - } - - /** - * Gets an engine group from the cache by ID. - * @param engineGroupId - The ID of the engine group to retrieve - * @returns The FlutterEngineGroup instance, or null if not found - */ - get(engineGroupId: string): FlutterEngineGroup | null { - return this.cachedEngineGroups.get(engineGroupId) ?? null; - } - - /** - * Puts an engine group into the cache or removes it if null is provided. - * @param engineGroupId - The ID of the engine group - * @param engineGroup - The FlutterEngineGroup instance to cache, or undefined to remove - */ - put(engineGroupId: string, engineGroup?: FlutterEngineGroup) { - if (engineGroup != null) { - this.cachedEngineGroups.set(engineGroupId, engineGroup); - } else { - this.cachedEngineGroups.delete(engineGroupId); - } - } - - /** - * Clears all engine groups from the cache. - */ - clear(): void { - this.cachedEngineGroups.clear(); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEnginePreload.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEnginePreload.ets deleted file mode 100644 index 40da2a1..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEnginePreload.ets +++ /dev/null @@ -1,213 +0,0 @@ -/* -* Copyright (c) 2025 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import FlutterEngine, { EngineLifecycleListener } from "./FlutterEngine" -import common from '@ohos.app.ability.common' -import display from '@ohos.display'; -import FlutterLoader from './loader/FlutterLoader' -import Log from '../../util/Log'; -import FlutterManager from '../ohos/FlutterManager'; -import FlutterInjector from '../../FlutterInjector' -import FlutterEngineCache from './FlutterEngineCache'; -import FlutterEngineGroupCache from './FlutterEngineGroupCache'; -import FlutterAbilityLaunchConfigs from '../ohos/FlutterAbilityLaunchConfigs'; -import JSONMethodCodec from '../../plugin/common/JSONMethodCodec'; -import MethodCall from '../../plugin/common/MethodCall'; -import FlutterNapi from './FlutterNapi'; -import { ViewportMetrics } from '../../view/FlutterView'; - -const TAG = "FlutterEnginePreload" - -/** - * Utility class for preloading Flutter engines. - * This class provides static methods to preload and prepare Flutter engines - * before they are actually displayed, improving startup performance. - */ -export default class FlutterEnginePreload { - /** - * Preloads a Flutter engine with the specified parameters. - * @param context - The application context - * @param params - Parameters for engine preloading, including entrypoint, route, etc. - * @param nextViewId - Optional view ID for the next Flutter view - */ - static async preloadEngine(context: common.Context, params: Record = {}, - nextViewId: string | null = null) { - let loader: FlutterLoader = FlutterInjector.getInstance().getFlutterLoader(); - let dartArgs = new Array(); - if (params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS]) { - dartArgs = params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS] as Array; - } - - if (!loader.initialized) { - loader.startInitialization(context); - loader.ensureInitializationComplete(dartArgs); - } - - let flutterNapi: FlutterNapi | null = - FlutterEnginePreload.preLoadFlutterNapi(context, loader.findAppBundlePath(), params) - let viewportMetrics: ViewportMetrics = new ViewportMetrics(); - if (params[FlutterAbilityLaunchConfigs.PRELOAD_VIEWPORT_METRICS_KEY]) { - viewportMetrics = params[FlutterAbilityLaunchConfigs.PRELOAD_VIEWPORT_METRICS_KEY] as ViewportMetrics; - } else { - let display_info: display.Display = display.getDefaultDisplaySync(); - viewportMetrics.physicalWidth = display_info.width; - viewportMetrics.physicalHeight = display_info.height; - viewportMetrics.devicePixelRatio = display_info.densityPixels; - viewportMetrics.physicalTouchSlop = 1.0 * display_info.densityPixels; - } - if (flutterNapi) { - if (!nextViewId) { - nextViewId = FlutterManager.getInstance().getNextFlutterViewId(); - } - flutterNapi.setPreloading(); - flutterNapi.xComponentPreDraw(nextViewId, viewportMetrics.physicalWidth, viewportMetrics.physicalHeight); - flutterNapi.setViewportMetrics(viewportMetrics.devicePixelRatio, - viewportMetrics.physicalWidth, - viewportMetrics.physicalHeight, - viewportMetrics.physicalViewPaddingTop, - viewportMetrics.physicalViewPaddingRight, - viewportMetrics.physicalViewPaddingBottom, - viewportMetrics.physicalViewPaddingLeft, - viewportMetrics.physicalViewInsetTop, - viewportMetrics.physicalViewInsetRight, - viewportMetrics.physicalViewInsetBottom, - viewportMetrics.physicalViewInsetLeft, - viewportMetrics.systemGestureInsetTop, - viewportMetrics.systemGestureInsetRight, - viewportMetrics.systemGestureInsetBottom, - viewportMetrics.systemGestureInsetLeft, - viewportMetrics.physicalTouchSlop, - new Array(0), - new Array(0), - new Array(0) - ); - } - } - - /** - * Prepares an existing engine for drawing by setting up viewport metrics and pre-drawing. - * @param engine - The FlutterEngine instance to prepare - * @param params - Parameters for engine preparation, including viewport metrics - * @param nextViewId - Optional view ID for the next Flutter view - */ - static predrawEngine(engine: FlutterEngine, params: Record = {}, nextViewId: string | null = null) { - if (!engine) { - return; - } - let flutterNapi = engine.getFlutterNapi(); - if (!flutterNapi.isAttached()) { - flutterNapi.attachToNative(); - } - if (!nextViewId) { - nextViewId = FlutterManager.getInstance().getNextFlutterViewId(); - } - let viewportMetrics: ViewportMetrics = new ViewportMetrics(); - if (params[FlutterAbilityLaunchConfigs.PRELOAD_VIEWPORT_METRICS_KEY]) { - viewportMetrics = params[FlutterAbilityLaunchConfigs.PRELOAD_VIEWPORT_METRICS_KEY] as ViewportMetrics; - } else { - let display_info: display.Display = display.getDefaultDisplaySync(); - viewportMetrics.physicalWidth = display_info.width; - viewportMetrics.physicalHeight = display_info.height; - viewportMetrics.devicePixelRatio = display_info.densityPixels; - viewportMetrics.physicalTouchSlop = 1.0 * display_info.densityPixels; - } - flutterNapi.setPreloading(); - flutterNapi.xComponentPreDraw(nextViewId, viewportMetrics.physicalWidth, viewportMetrics.physicalHeight); - flutterNapi.setViewportMetrics(viewportMetrics.devicePixelRatio, - viewportMetrics.physicalWidth, - viewportMetrics.physicalHeight, - viewportMetrics.physicalViewPaddingTop, - viewportMetrics.physicalViewPaddingRight, - viewportMetrics.physicalViewPaddingBottom, - viewportMetrics.physicalViewPaddingLeft, - viewportMetrics.physicalViewInsetTop, - viewportMetrics.physicalViewInsetRight, - viewportMetrics.physicalViewInsetBottom, - viewportMetrics.physicalViewInsetLeft, - viewportMetrics.systemGestureInsetTop, - viewportMetrics.systemGestureInsetRight, - viewportMetrics.systemGestureInsetBottom, - viewportMetrics.systemGestureInsetLeft, - viewportMetrics.physicalTouchSlop, - new Array(0), - new Array(0), - new Array(0) - ); - } - - /** - * Preloads a FlutterNapi instance with the specified configuration. - * @param context - The application context - * @param bundlePath - Path to the Flutter bundle - * @param params - Parameters for NAPI preloading, including cached engine ID, entrypoint, etc. - * @returns The preloaded FlutterNapi instance, or null if preloading fails - */ - static preLoadFlutterNapi(context: common.Context, bundlePath: string, - params: Record = {}): FlutterNapi | null { - - let cachedEngineId = params[FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] as string; - let cachedEngineGroupId = params[FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID] as string; - - let dartEntrypoint = FlutterAbilityLaunchConfigs.DEFAULT_DART_ENTRYPOINT; - if (params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT]) { - dartEntrypoint = params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT] as string; - } - - let dartEntrypointLibraryUri = ""; - if (params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_LIBRARY_URI]) { - dartEntrypointLibraryUri = params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_LIBRARY_URI] as string; - } - - let initialRoute = ""; - if (params[FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE]) { - initialRoute = params[FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE] as string - } - - let dartArgs = new Array(); - if (params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS]) { - dartArgs = params[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS] as Array; - } - - Log.d(TAG, "cachedEngineId=" + cachedEngineId); - let flutterNapi: FlutterNapi | null = null; - if (cachedEngineId && cachedEngineId.length > 0) { - let engine = FlutterEngineCache.getInstance().get(cachedEngineId); - if (engine) { - flutterNapi = engine.getFlutterNapi(); - } - } - if (cachedEngineGroupId && cachedEngineGroupId.length > 0) { - let flutterEngineGroup = FlutterEngineGroupCache.instance.get(cachedEngineGroupId); - if (flutterEngineGroup) { - let defaultEngine = flutterEngineGroup.getDefaultEngine(); - if (defaultEngine) { - let oldFlutterNapi = defaultEngine.getFlutterNapi(); - return oldFlutterNapi.preSpawn(dartEntrypoint, dartEntrypointLibraryUri, initialRoute, dartArgs); - } - } - } - - if (!flutterNapi) { - flutterNapi = FlutterInjector.getInstance().getPreloadFlutterNapi(); - } - if (flutterNapi) { - if (!flutterNapi.isAttached()) { - flutterNapi.attachToNative(); - } - Log.d(TAG, "setInitialRoute: " + initialRoute); - let message = JSONMethodCodec.INSTANCE.encodeMethodCall(new MethodCall("setInitialRoute", initialRoute)); - flutterNapi.dispatchPlatformMessage("flutter/navigation", message, message.byteLength, 0); - - flutterNapi.runBundleAndSnapshotFromLibrary( - bundlePath, - dartEntrypoint, - dartEntrypointLibraryUri, - context.resourceManager, - dartArgs); - } - return flutterNapi; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterNapi.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterNapi.ets deleted file mode 100644 index 50c6282..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterNapi.ets +++ /dev/null @@ -1,1162 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import flutter from 'libflutter.so'; -import common from '@ohos.app.ability.common'; -import Log from '../../util/Log'; -import resourceManager from '@ohos.resourceManager'; -import { PlatformMessageHandler } from './dart/PlatformMessageHandler'; -import { FlutterCallbackInformation } from '../../view/FlutterCallbackInformation'; -import image from '@ohos.multimedia.image'; -import { EngineLifecycleListener } from './FlutterEngine'; -import { ByteBuffer } from '../../util/ByteBuffer'; -import LocalizationPlugin from '../../plugin/localization/LocalizationPlugin'; -import i18n from '@ohos.i18n'; -import Any from '../../plugin/common/Any'; -import FlutterManager from '../ohos/FlutterManager'; -import deviceInfo from '@ohos.deviceInfo'; -import TouchEventProcessor from '../ohos/TouchEventProcessor'; -import BuildProfile from '../../../../../BuildProfile'; -import { Action } from '../engine/systemchannels/AccessibilityChannel'; - -const TAG = "FlutterNapi"; - -enum ContextType { - APP_LIFECYCLE = 0, - JS_PAGE_LIFECYCLE, -} - -/** - * Represents a pending platform message that is queued during preloading. - * These messages are processed once the Flutter engine is ready to handle them. - */ -interface PendingMessage { - /** The channel name for the message. */ - channel: string; - /** The message data as an ArrayBuffer. */ - message: ArrayBuffer; - /** The reply ID for responding to the message. */ - replyId: number; - /** Additional message data. */ - messageData: number; -} - -/** - * Provides Flutter NAPI interface for ArkTS. - * This class serves as the bridge between the ArkTS layer and the native Flutter engine, - * handling initialization, message passing, viewport management, and lifecycle events. - */ -export default class FlutterNapi { - private static hasInit: boolean = false; - /** Whether the native methods have been implemented. */ - hasImplemented: boolean = false; - /** The native shell holder ID, or null if not attached. */ - nativeShellHolderId: number | null = null; - /** The platform message handler for receiving messages from Dart, or null if not set. */ - platformMessageHandler: PlatformMessageHandler | null = null; - private engineLifecycleListeners = new Set(); - /** The accessibility delegate for handling accessibility events, or null if not set. */ - accessibilityDelegate: AccessibilityDelegate | null = null; - /** The localization plugin for handling locale information, or null if not set. */ - localizationPlugin: LocalizationPlugin | null = null; - /** Whether Flutter UI is currently being displayed. */ - isDisplayingFlutterUi: boolean = false; - /** Whether Flutter UI has been preloaded. */ - isPreloadedFlutterUi: boolean = false; - /** Whether Dart code is currently running. */ - isRunningDart: boolean = false; - private nextSpawnNapi: FlutterNapi | null = null; - private pendingMessages: PendingMessage[] = []; - private readyForHandleMessage: boolean = true; - private firstPreloading: boolean = true; - - /** - * Updates the refresh rate for the Flutter engine. - * @param refreshRateFPS - The refresh rate in frames per second - */ - updateRefreshRate(refreshRateFPS: number) { - flutter.nativeUpdateRefreshRate(refreshRateFPS); - } - - /** - * Updates the size of the Flutter view. - * @param width - The new width in pixels - * @param height - The new height in pixels - */ - updateSize(width: number, height: number) { - flutter.nativeUpdateSize(width, height); - } - - /** - * Updates the pixel density of the display. - * @param densityPixels - The pixel density value (dots per inch) - */ - updateDensity(densityPixels: number) { - flutter.nativeUpdateDensity(densityPixels); - } - - /** - * Initializes the Flutter engine with the specified parameters. - * @param context - The application context - * @param args - Command-line arguments for the Flutter engine - * @param bundlePath - Path to the Flutter bundle - * @param appStoragePath - Path to the application storage directory - * @param engineCachesPath - Path to the engine caches directory - * @param initTimeMillis - Initialization time in milliseconds - */ - init(context: common.Context, - args: Array, - bundlePath: string, - appStoragePath: string, - engineCachesPath: string, - initTimeMillis: number) { - if (FlutterNapi.hasInit) { - Log.e(TAG, "the engine has init"); - return; - } - Log.w(TAG, "HAR_VERSION=" + BuildProfile.HAR_VERSION); - Log.d(TAG, JSON.stringify({ - "name": "init, initTimeMillis=" + initTimeMillis, - "bundlePath": bundlePath, - "appStoragePath": appStoragePath, - "engineCachesPath": engineCachesPath, - "args": args, - })); - let code: number | null = flutter.nativeInit(context, args, bundlePath, appStoragePath, - engineCachesPath, initTimeMillis, deviceInfo.productModel); - FlutterNapi.hasInit = code == 0; - Log.d(TAG, "init code=" + code + ", FlutterNapi.hasInit" + FlutterNapi.hasInit); - } - - /** - * Prefetches the default font manager. - * This should be called before using fonts in the Flutter engine. - */ - static prefetchDefaultFontManager(): void { - flutter.nativePrefetchDefaultFontManager(); - } - - /** - * Checks and reloads fonts for this engine instance. - */ - checkAndReloadFont(): void { - flutter.nativeCheckAndReloadFont(this.nativeShellHolderId!); - } - - /** - * Attaches this FlutterNapi instance to the native engine. - * This must be called before using most FlutterNapi methods. - */ - attachToNative(): void { - if (!FlutterNapi.hasInit) { - Log.e(TAG, "attachToNative fail, FlutterNapi.hasInit=" + FlutterNapi.hasInit); - return; - } - if (this.nativeShellHolderId == null) { - this.nativeShellHolderId = flutter.nativeAttach(this); - } - Log.d(TAG, "nativeShellHolderId=" + this.nativeShellHolderId); - } - - /** - * Runs a Flutter bundle and snapshot from a library. - * @param bundlePath - Path to the Flutter bundle - * @param entrypointFunctionName - Name of the entrypoint function - * @param pathToEntrypointFunction - Path to the entrypoint function - * @param assetManager - Resource manager for accessing assets - * @param entrypointArgs - Arguments to pass to the entrypoint function - */ - runBundleAndSnapshotFromLibrary( - bundlePath: string, - entrypointFunctionName: string | undefined, - pathToEntrypointFunction: string | undefined, - assetManager: resourceManager.ResourceManager, - entrypointArgs: Array) { - if (!FlutterNapi.hasInit) { - Log.e(TAG, "runBundleAndSnapshotFromLibrary fail, FlutterNapi.hasInit=" + FlutterNapi.hasInit); - return; - } - Log.d(TAG, "init: bundlePath=" + bundlePath + " entrypointFunctionName=" + entrypointFunctionName + - " pathToEntrypointFunction=" + pathToEntrypointFunction + " entrypointArgs=" + JSON.stringify(entrypointArgs)) - if (!this.nativeShellHolderId) { - Log.e(TAG, "runBundleAndSnapshotFromLibrary this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeRunBundleAndSnapshotFromLibrary(this.nativeShellHolderId!, bundlePath, entrypointFunctionName, - pathToEntrypointFunction, assetManager, entrypointArgs); - this.isRunningDart = true; - this.isDisplayingFlutterUi = false; - this.isPreloadedFlutterUi = false; - }; - - /** - * Checks if the native methods are implemented. - * @param methodName - Optional method name for logging - * @returns true if methods are implemented, false otherwise - */ - checkImplemented(methodName: string = ""): boolean { - if (!this.hasImplemented) { - Log.e(TAG, "this method has not implemented -> " + methodName) - } - return this.hasImplemented; - } - - /** - * Sets the platform message handler for receiving messages from Dart. - * @param platformMessageHandler - The PlatformMessageHandler instance, or null to remove - */ - setPlatformMessageHandler(platformMessageHandler: PlatformMessageHandler | null): void { - this.ensureRunningOnMainThread(); - this.platformMessageHandler = platformMessageHandler; - } - - private nativeNotifyLowMemoryWarning(nativeShellHolderId: number): void { - - } - - /** - * Looks up callback information for a given handler. - * @param handle - The handler number - * @returns The FlutterCallbackInformation, or null if not found - */ - static nativeLookupCallbackInformation(handle: number): FlutterCallbackInformation | null { - let callbackInformation = new FlutterCallbackInformation(); - let ret: number = flutter.nativeLookupCallbackInformation(callbackInformation, handle); - if (ret == 0) { - return callbackInformation; - } - return null; - } - - /** - * Notifies the Flutter engine of a low memory warning. - */ - notifyLowMemoryWarning(): void { - this.ensureRunningOnMainThread(); - if (!this.nativeShellHolderId) { - Log.e(TAG, "notifyLowMemoryWarning this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - this.nativeNotifyLowMemoryWarning(this.nativeShellHolderId!); - } - - /** - * Checks if this FlutterNapi instance is attached to the native engine. - * @returns true if attached, false otherwise - */ - isAttached(): boolean { - return this.nativeShellHolderId != null; - } - - /** - * Ensures that the current code is running on the main thread. - */ - private ensureRunningOnMainThread(): void { - - } - - /** - * Dispatches an empty platform message to Dart. - * @param channel - The channel name for the message - * @param responseId - The response ID for receiving a reply - */ - dispatchEmptyPlatformMessage(channel: String, responseId: number): void { - this.ensureRunningOnMainThread(); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "dispatchEmptyPlatformMessage this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeDispatchEmptyPlatformMessage(this.nativeShellHolderId!, channel, responseId); - } else { - Log.w( - TAG, - "Tried to send a platform message to Flutter, but FlutterNapi was detached from native C++. Could not send. Channel: " - + channel - + ". Response ID: " - + responseId); - } - } - - /** - * Sends a platform message with data from OpenHarmony to Flutter over the given channel. - * @param channel - The channel name for the message - * @param message - The message data as an ArrayBuffer - * @param position - The position in the message buffer - * @param responseId - The response ID for receiving a reply - */ - dispatchPlatformMessage(channel: String, message: ArrayBuffer, position: number, responseId: number): void { - this.ensureRunningOnMainThread(); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "dispatchPlatformMessage this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeDispatchPlatformMessage(this.nativeShellHolderId!, channel, message, position, responseId); - } else { - Log.w( - TAG, - "Tried to send a platform message to Flutter, but FlutterNapi was detached from native C++. Could not send. Channel: " - + channel - + ". Response ID: " - + responseId); - } - } - - /** - * Invokes an empty response callback for a platform message. - * @param responseId - The response ID that was sent with the original message - */ - invokePlatformMessageEmptyResponseCallback(responseId: number): void { - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "invokePlatformMessageEmptyResponseCallback this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeInvokePlatformMessageEmptyResponseCallback(this.nativeShellHolderId!, responseId); - } else { - Log.w( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send. Response ID: " - + responseId); - } - } - - /** - * Invokes a response callback with data for a platform message. - * @param responseId - The response ID that was sent with the original message - * @param message - The reply data as an ArrayBuffer - * @param position - The position in the message buffer - */ - invokePlatformMessageResponseCallback(responseId: number, message: ArrayBuffer, position: number) { - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeInvokePlatformMessageResponseCallback( - this.nativeShellHolderId!, responseId, message, position); - } else { - Log.w( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send. Response ID: " - + responseId); - } - } - - /** - * Sets the viewport metrics for the Flutter view. - * @param devicePixelRatio - The device pixel ratio - * @param physicalWidth - Physical width in pixels - * @param physicalHeight - Physical height in pixels - * @param physicalPaddingTop - Top padding in physical pixels - * @param physicalPaddingRight - Right padding in physical pixels - * @param physicalPaddingBottom - Bottom padding in physical pixels - * @param physicalPaddingLeft - Left padding in physical pixels - * @param physicalViewInsetTop - Top view inset in physical pixels - * @param physicalViewInsetRight - Right view inset in physical pixels - * @param physicalViewInsetBottom - Bottom view inset in physical pixels - * @param physicalViewInsetLeft - Left view inset in physical pixels - * @param systemGestureInsetTop - Top system gesture inset - * @param systemGestureInsetRight - Right system gesture inset - * @param systemGestureInsetBottom - Bottom system gesture inset - * @param systemGestureInsetLeft - Left system gesture inset - * @param physicalTouchSlop - Physical touch slop value - * @param displayFeaturesBounds - Array of display feature bounds - * @param displayFeaturesType - Array of display feature types - * @param displayFeaturesState - Array of display feature states - */ - setViewportMetrics(devicePixelRatio: number, physicalWidth: number - , physicalHeight: number, physicalPaddingTop: number, physicalPaddingRight: number - , physicalPaddingBottom: number, physicalPaddingLeft: number, physicalViewInsetTop: number - , physicalViewInsetRight: number, physicalViewInsetBottom: number, physicalViewInsetLeft: number - , systemGestureInsetTop: number, systemGestureInsetRight: number, systemGestureInsetBottom: number - , systemGestureInsetLeft: number, physicalTouchSlop: number, displayFeaturesBounds: Array - , displayFeaturesType: Array, displayFeaturesState: Array): void { - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "setViewportMetrics this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeSetViewportMetrics(this.nativeShellHolderId!, devicePixelRatio, - physicalWidth, - physicalHeight, - physicalPaddingTop, - physicalPaddingRight, - physicalPaddingBottom, - physicalPaddingLeft, - physicalViewInsetTop, - physicalViewInsetRight, - physicalViewInsetBottom, - physicalViewInsetLeft, - systemGestureInsetTop, - systemGestureInsetRight, - systemGestureInsetBottom, - systemGestureInsetLeft, - physicalTouchSlop, - displayFeaturesBounds, - displayFeaturesType, - displayFeaturesState); - } - } - - /** - * Spawns a new FlutterNapi instance from this instance. - * @param entrypointFunctionName - Name of the entrypoint function - * @param pathToEntrypointFunction - Path to the entrypoint function - * @param initialRoute - Initial route for navigation - * @param entrypointArgs - Arguments to pass to the entrypoint function - * @returns A new FlutterNapi instance - */ - spawn(entrypointFunctionName: string, pathToEntrypointFunction: string, initialRoute: string, - entrypointArgs: Array): FlutterNapi { - if (this.nextSpawnNapi) { - let ret = this.nextSpawnNapi; - this.nextSpawnNapi = null; - return ret; - } - let flutterNapi = new FlutterNapi(); - let shellHolderId: number = - flutter.nativeSpawn(this.nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute, - entrypointArgs, flutterNapi); - flutterNapi.nativeShellHolderId = shellHolderId; - flutterNapi.isRunningDart = this.isRunningDart; - flutterNapi.isDisplayingFlutterUi = false; - flutterNapi.isPreloadedFlutterUi = false; - return flutterNapi; - } - - /** - * Pre-spawns a new FlutterNapi instance for later use. - * @param entrypointFunctionName - Name of the entrypoint function - * @param pathToEntrypointFunction - Path to the entrypoint function - * @param initialRoute - Initial route for navigation - * @param entrypointArgs - Arguments to pass to the entrypoint function - * @returns A new FlutterNapi instance that will be used on the next spawn call - */ - preSpawn(entrypointFunctionName: string, pathToEntrypointFunction: string, initialRoute: string, - entrypointArgs: Array): FlutterNapi { - if (this.nextSpawnNapi) { - this.nextSpawnNapi.detachFromNativeAndReleaseResources(); - } - let flutterNapi = new FlutterNapi(); - let shellHolderId: number = - flutter.nativeSpawn(this.nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute, - entrypointArgs, flutterNapi); - flutterNapi.nativeShellHolderId = shellHolderId; - flutterNapi.isRunningDart = this.isRunningDart; - flutterNapi.isDisplayingFlutterUi = false; - flutterNapi.isPreloadedFlutterUi = false; - this.nextSpawnNapi = flutterNapi; - return flutterNapi; - } - - /** - * Adds an engine lifecycle listener. - * @param engineLifecycleListener - The EngineLifecycleListener to add - */ - addEngineLifecycleListener(engineLifecycleListener: EngineLifecycleListener): void { - this.engineLifecycleListeners.add(engineLifecycleListener); - } - - /** - * Removes an engine lifecycle listener. - * @param engineLifecycleListener - The EngineLifecycleListener to remove - */ - removeEngineLifecycleListener(engineLifecycleListener: EngineLifecycleListener) { - this.engineLifecycleListeners.delete(engineLifecycleListener); - } - - /** - * Called by native to respond to a platform message that we sent. - * @param replyId - The response ID that was sent with the original message - * @param reply - The reply data as an ArrayBuffer - */ - handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void { - Log.d(TAG, "called handlePlatformMessageResponse Response ID: " + replyId); - if (this.platformMessageHandler != null) { - this.platformMessageHandler.handlePlatformMessageResponse(replyId, reply); - } - } - - /** - * Called by native on any thread to handle a platform message from Dart. - * @param channel - The channel name for the message - * @param message - The message data as an ArrayBuffer - * @param replyId - The reply ID for responding to the message - * @param messageData - Additional message data - */ - handlePlatformMessage(channel: string, message: ArrayBuffer, replyId: number, messageData: number): void { - Log.d(TAG, "called handlePlatformMessage Channel: " + channel + ". Response ID: " + replyId); - if (this.platformMessageHandler != null && this.readyForHandleMessage) { - this.platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData); - } else { - const pendingMessage: PendingMessage = { - channel, - message, - replyId, - messageData - }; - this.pendingMessages.push(pendingMessage); - } - } - - /** - * Sets the preloading state, preventing message handling until ready. - */ - setPreloading(): void { - if (this.firstPreloading) { - this.readyForHandleMessage = false; - this.firstPreloading = false; - } - } - - /** - * Processes all pending messages that were queued during preloading. - */ - processPendingMessages(): void { - Log.d(TAG, "processPendingMessages len:" + this.pendingMessages.length); - this.readyForHandleMessage = true; - while (this.pendingMessages.length > 0 && this.platformMessageHandler) { - const pendingMessage = this.pendingMessages.shift(); - if (pendingMessage) { - this.platformMessageHandler.handleMessageFromDart( - pendingMessage.channel, - pendingMessage.message, - pendingMessage.replyId, - pendingMessage.messageData - ); - } - } - } - - /** - * Called by native to notify that the first Flutter frame has been rendered. - * @param isPreload - Whether this is a preload frame (1) or a regular frame (0) - */ - onFirstFrame(isPreload: number): void { - Log.d(TAG, "called onFirstFrame isPreload:" + isPreload); - if (isPreload) { - this.isPreloadedFlutterUi = true; - } else { - this.processPendingMessages(); - if (this.isDisplayingFlutterUi) { - return; - } - this.isDisplayingFlutterUi = true; - } - FlutterManager.getInstance().getFlutterViewList().forEach((value) => { - if (this.nativeShellHolderId != null && value.isSameEngineShellHolderId(this.nativeShellHolderId)) { - value.onFirstFrame(isPreload); - } - }); - } - - /** - * Called by native when the engine is about to restart. - * Notifies all registered lifecycle listeners. - */ - onPreEngineRestart(): void { - Log.d(TAG, "called onPreEngineRestart") - this.engineLifecycleListeners.forEach(listener => listener.onPreEngineRestart()); - } - - /** - * Computes the platform-resolved locale from the given locale strings. - * Invoked by native to obtain the results of OpenHarmony's locale resolution algorithm. - * @param strings - Array of locale strings - * @returns Array of resolved locale strings - */ - computePlatformResolvedLocale(strings: Array): Array { - Log.d(TAG, "called computePlatformResolvedLocale " + JSON.stringify(strings)) - return [] - } - - /** - * Sets whether semantics are enabled and sends a response. - * @param enabled - Whether to enable semantics - * @param responseId - The response ID for the reply - */ - setSemanticsEnabledWithRespId(enabled: boolean, responseId: number): void { - this.ensureRunningOnMainThread(); - if (this.isAttached()) { - flutter.nativeSetSemanticsEnabled(this.nativeShellHolderId!, enabled); - } else { - Log.w( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send. Response ID: " - + responseId); - } - } - - /** - * Sets whether semantics are enabled. - * @param enabled - Whether to enable semantics - */ - setSemanticsEnabled(enabled: boolean): void { - this.ensureRunningOnMainThread(); - if (this.isAttached()) { - flutter.nativeSetSemanticsEnabled(this.nativeShellHolderId!, enabled); - } else { - Log.e( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send."); - } - } - - /** - * Sets accessibility features and sends a response. - * @param accessibilityFeatureFlags - The accessibility feature flags - * @param responseId - The response ID for the reply - */ - setAccessibilityFeatures(accessibilityFeatureFlags: number, responseId: number): void { - if (this.isAttached()) { - flutter.nativeSetAccessibilityFeatures(accessibilityFeatureFlags, responseId); - } else { - Log.w( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send. Response ID: " - + responseId); - } - } - - /** - * Native method for setting accessibility features. - * @param accessibilityFeatureFlags - The accessibility feature flags - * @param responseId - The response ID for the reply - */ - nativeSetAccessibilityFeatures(accessibilityFeatureFlags: number, responseId: number): void { - } - - /** - * Dispatches a semantics action to the native engine. - * @param virtualViewId - The virtual view ID - * @param action - The semantics action to dispatch - * @param responseId - The response ID for the reply - */ - dispatchSemanticsAction(virtualViewId: number, action: Action, responseId: number): void { - if (this.isAttached()) { - this.nativeDispatchSemanticsAction(virtualViewId, action, responseId); - } else { - Log.w( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send. Response ID: " - + responseId); - } - } - - /** - * Native method for dispatching a semantics action. - * @param virtualViewId - The virtual view ID - * @param action - The semantics action to dispatch - * @param responseId - The response ID for the reply - */ - nativeDispatchSemanticsAction(virtualViewId: number, action: Action, responseId: number): void { - } - - /** - * Sets the accessibility delegate for handling accessibility events. - * @param delegate - The AccessibilityDelegate instance - * @param responseId - The response ID for the reply - */ - setAccessibilityDelegate(delegate: AccessibilityDelegate, responseId: number): void { - if (this.isAttached()) { - this.accessibilityDelegate = delegate; - } else { - Log.w( - TAG, - "Tried to send a platform message response, but FlutterNapi was detached from native C++. Could not send. Response ID: " - + responseId); - } - } - - /** - * Called when the accessibility state changes. - * @param state - Whether accessibility is enabled - */ - accessibilityStateChange(state: Boolean): void { - this.ensureRunningOnMainThread(); - if (this.accessibilityDelegate != null) { - this.accessibilityDelegate.accessibilityStateChange(state); - } - Log.d(TAG, "accessibilityStateChange: state is " + state ? "on" : "off"); - if (this.nativeShellHolderId != null) { - flutter.nativeAccessibilityStateChange(this.nativeShellHolderId!, state); - } else { - Log.w(TAG, "accessibilityStateChange, nativeShellHolderId is null") - } - } - - /** - * Sets the localization plugin for handling locale information. - * @param localizationPlugin - The LocalizationPlugin instance, or null to remove - */ - setLocalizationPlugin(localizationPlugin: LocalizationPlugin | null): void { - this.localizationPlugin = localizationPlugin; - } - - /** - * Gets the system language list from the platform. - */ - getSystemLanguages() { - Log.d(TAG, "called getSystemLanguages ") - let index: number; - let systemLanguages = i18n.System.getPreferredLanguageList(); - for (index = 0; index < systemLanguages.length; index++) { - Log.d(TAG, "systemlanguages " + index + ":" + systemLanguages[index]); - } - if (!this.nativeShellHolderId) { - Log.e(TAG, "getSystemLanguages this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeGetSystemLanguages(this.nativeShellHolderId!, systemLanguages); - } - - /** - * Attaches a FlutterEngine to an XComponent. - * @param xcomponentId - The XComponent ID - */ - xComponentAttachFlutterEngine(xcomponentId: string) { - flutter.nativeXComponentAttachFlutterEngine(xcomponentId, this.nativeShellHolderId!); - } - - /** - * Pre-renders an XComponent. - * @param xcomponentId - The XComponent ID - * @param width - The width of the component - * @param height - The height of the component - */ - xComponentPreDraw(xcomponentId: string, width: number, height: number) { - flutter.nativeXComponentPreDraw(xcomponentId, this.nativeShellHolderId!, width, height); - } - - /** - * Detaches a FlutterEngine from an XComponent. - * @param xcomponentId - The XComponent ID - */ - xComponentDetachFlutterEngine(xcomponentId: string) { - flutter.nativeXComponentDetachFlutterEngine(xcomponentId, this.nativeShellHolderId!); - } - - /** - * Dispatches a mouse wheel event from an XComponent to the Flutter engine. - * @param xcomponentId - The XComponent ID - * @param eventType - The type of the mouse wheel event - * @param event - The pan gesture event containing mouse wheel data - */ - xComponentDisPatchMouseWheel(xcomponentId: string, eventType: string, event: PanGestureEvent) { - // only mouse - if (event.source !== SourceType.Mouse) { - return; - } - const vaildFinger = event.fingerList?.find(item => item.globalX && item.globalY); - if (!vaildFinger) { - return; - } - flutter.nativeXComponentDispatchMouseWheel( - this.nativeShellHolderId!!, - xcomponentId, - eventType, - vaildFinger?.id, - vaildFinger?.localX, - vaildFinger?.localY, - event.offsetY, - event.timestamp - ); - } - - /** - * Detaches this FlutterNapi instance from the native engine and releases all resources. - */ - detachFromNativeAndReleaseResources() { - if (!this.nativeShellHolderId) { - Log.e(TAG, "detachFromNativeAndReleaseResources this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeDestroy(this.nativeShellHolderId!!); - this.nativeShellHolderId = null; - this.isRunningDart = false; - this.isDisplayingFlutterUi = false; - this.isPreloadedFlutterUi = false; - this.readyForHandleMessage = false; - } - - /** - * Unregisters a texture from the Flutter engine. - * @param textureId - The texture ID to unregister - */ - unregisterTexture(textureId: number): void { - Log.d(TAG, "called unregisterTexture "); - if (!this.nativeShellHolderId) { - Log.e(TAG, "unregisterTexture this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeUnregisterTexture(this.nativeShellHolderId!, textureId); - } - - /** - * Registers a PixelMap as a texture in the Flutter engine. - * @param textureId - The texture ID - * @param pixelMap - The PixelMap to register - */ - registerPixelMap(textureId: number, pixelMap: PixelMap): void { - Log.d(TAG, "called registerPixelMap "); - if (!this.nativeShellHolderId) { - Log.e(TAG, "registerPixelMap this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeRegisterPixelMap(this.nativeShellHolderId!, textureId, pixelMap); - } - - /** - * Sets the background PixelMap for a texture. - * @param textureId - The texture ID - * @param pixelMap - The PixelMap to use as background - */ - setTextureBackGroundPixelMap(textureId: number, pixelMap: PixelMap): void { - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return; - } - flutter.nativeSetTextureBackGroundPixelMap(this.nativeShellHolderId!, textureId, pixelMap); - } else { - return; - } - } - - /** - * Sets the background color for a texture. - * @param textureId - The texture ID - * @param color - The color value in ARGB format - */ - setTextureBackGroundColor(textureId: number, color: number): void { - Log.d(TAG, "called setTextureBackGroundColor"); - if (!this.isAttached()) { - Log.e(TAG, "setTextureBackGroundColor when napi is not attached"); - return; - } - flutter.nativeSetTextureBackGroundColor(this.nativeShellHolderId!, textureId, color); - } - - /** - * Registers a texture in the Flutter engine. - * @param textureId - The texture ID to register - * @returns The registered texture ID, or 0 if registration fails - */ - registerTexture(textureId: number): number { - Log.d(TAG, "called registerTexture "); - if (!this.nativeShellHolderId) { - Log.e(TAG, "registerTexture this.nativeShellHolderId = " + this.nativeShellHolderId) - return 0; - } - return flutter.nativeRegisterTexture(this.nativeShellHolderId!, textureId); - } - - /** - * @deprecated since 3.22 - * @useinstead FlutterNapi#getTextureNativeWindowPtr - */ - getTextureNativeWindowId(textureId: number): number { - Log.d(TAG, "called getTextureNativeWindowId "); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return 0; - } - return flutter.nativeGetTextureWindowId(this.nativeShellHolderId!, textureId); - } else { - return 0; - } - } - - /** - * Gets the native window pointer for a texture. - * @param textureId - The texture ID - * @returns The native window pointer as a bigint, or BigInt("0") if not available - */ - getTextureNativeWindowPtr(textureId: number): bigint { - Log.d(TAG, "called getTextureNativeWindowPtr"); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return BigInt("0"); - } - return flutter.nativeGetTextureWindowPtr(this.nativeShellHolderId!, textureId); - } else { - return BigInt("0"); - } - } - - /** - * @deprecated since 3.22 - * @useinstead FlutterNapi#setExternalNativeImagePtr - */ - setExternalNativeImage(textureId: number, native_image: number): boolean { - Log.d(TAG, "called setExternalNativeImage "); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return false; - } - return Boolean(flutter.nativeSetExternalNativeImage(this.nativeShellHolderId!, textureId, native_image)); - } else { - return false; - } - } - - /** - * Sets an external native image pointer for a texture. - * @param textureId - The texture ID - * @param native_image_ptr - The native image pointer as a bigint - * @returns true if successful, false otherwise - */ - setExternalNativeImagePtr(textureId: number, native_image_ptr: bigint): boolean { - Log.d(TAG, "called setExternalNativeImagePtr"); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return false; - } - return Boolean(flutter.nativeSetExternalNativeImagePtr(this.nativeShellHolderId!, textureId, native_image_ptr)); - } else { - return false; - } - } - - /** - * Resets an external texture. - * @param textureId - The texture ID - * @param need_surfaceId - Whether a surface ID is needed - * @returns The surface ID if needed, or 0 otherwise - */ - resetExternalTexture(textureId: number, need_surfaceId: boolean): number { - Log.d(TAG, "called resetExternalTexture "); - if (this.isAttached()) { - if (!this.nativeShellHolderId) { - Log.e(TAG, "this.nativeShellHolderId = " + this.nativeShellHolderId) - return 0; - } - return flutter.nativeResetExternalTexture(this.nativeShellHolderId!, textureId, need_surfaceId); - } else { - return 0; - } - } - - /** - * Sets the buffer size for a texture. - * @param textureId - The texture ID - * @param width - The width of the buffer - * @param height - The height of the buffer - */ - setTextureBufferSize(textureId: number, width: number, height: number): void { - Log.d(TAG, "called setTextureBufferSize "); - if (!this.isAttached()) { - Log.e(TAG, "setTextureBufferSize this.nativeShellHolderId:" + this.nativeShellHolderId) - return; - } - flutter.nativeSetTextureBufferSize(this.nativeShellHolderId!, textureId, width, height); - } - - /** - * Notifies the Flutter engine that a texture is being resized. - * @param textureId - The texture ID - * @param width - The new width - * @param height - The new height - */ - notifyTextureResizing(textureId: number, width: number, height: number): void { - Log.d(TAG, "called notifyTextureResizing "); - if (!this.isAttached()) { - Log.e(TAG, "notifyTextureResizing this.nativeShellHolderId:" + this.nativeShellHolderId) - return; - } - flutter.nativeNotifyTextureResizing(this.nativeShellHolderId!, textureId, width, height); - } - - /** - * Enables or disables frame caching for improved performance. - * @param enable - Whether to enable frame caching - */ - enableFrameCache(enable: boolean): void { - if (!this.nativeShellHolderId) { - return; - } - flutter.nativeEnableFrameCache(this.nativeShellHolderId!, enable); - } - - /** - * Handles touch events from the platform. - * @param strings - Array of strings containing touch event data - */ - onTouchEvent(strings: Array): void { - if (this.isAttached()) { - TouchEventProcessor.getInstance().postTouchEvent(strings); - } - } - - /** - * Handles mouse events from the platform. - * @param strings - Array of strings containing mouse event data - */ - onMouseEvent(strings: Array): void { - if (this.isAttached()) { - TouchEventProcessor.getInstance().postMouseEvent(strings); - } - } - - /** - * Handles axis events (scroll wheel, etc.) from the platform. - * @param strings - Array of strings containing axis event data - */ - onAxisEvent(strings: Array): void { - if (this.isAttached()) { - TouchEventProcessor.getInstance().postAxisEvent(strings); - } - } - - /** - * Checks if a Unicode code point is an emoji. - * @param code - The Unicode code point - * @returns true if the code point is an emoji, false otherwise - */ - static unicodeIsEmoji(code: number): boolean { - return Boolean(flutter.nativeUnicodeIsEmoji(code)); - } - - /** - * Checks if a Unicode code point is an emoji modifier. - * @param code - The Unicode code point - * @returns true if the code point is an emoji modifier, false otherwise - */ - static unicodeIsEmojiModifier(code: number): boolean { - return Boolean(flutter.nativeUnicodeIsEmojiModifier(code)); - } - - /** - * Checks if a Unicode code point is an emoji modifier base. - * @param code - The Unicode code point - * @returns true if the code point is an emoji modifier base, false otherwise - */ - static unicodeIsEmojiModifierBase(code: number): boolean { - return Boolean(flutter.nativeUnicodeIsEmojiModifierBase(code)); - } - - /** - * Checks if a Unicode code point is a variation selector. - * @param code - The Unicode code point - * @returns true if the code point is a variation selector, false otherwise - */ - static unicodeIsVariationSelector(code: number): boolean { - return Boolean(flutter.nativeUnicodeIsVariationSelector(code)); - } - - /** - * Checks if a Unicode code point is a regional indicator symbol. - * @param code - The Unicode code point - * @returns true if the code point is a regional indicator symbol, false otherwise - */ - static unicodeIsRegionalIndicatorSymbol(code: number): boolean { - return Boolean(flutter.nativeUnicodeIsRegionalIndicatorSymbol(code)); - } - - /** - * Sets the font weight scale for text rendering. - * @param fontWeightScale - The font weight scale factor - */ - setFontWeightScale(fontWeightScale: number): void { - this.ensureRunningOnMainThread(); - if (this.isAttached()) { - Log.i(TAG, "setFontWeightScale: " + fontWeightScale); - flutter.nativeSetFontWeightScale(this.nativeShellHolderId!, fontWeightScale); - } else { - Log.w(TAG, "setFontWeightScale is detached !"); - } - } - - /** - * Sets the Flutter navigation action state. - * @param shellHolderId - The shell holder ID - * @param isNavigate - Whether navigation is active - */ - setFlutterNavigationAction(shellHolderId: number, isNavigate: boolean): void { - this.ensureRunningOnMainThread(); - if (this.isAttached()) { - Log.i(TAG, "setFlutterNavigationAction: " + isNavigate); - flutter.nativeSetFlutterNavigationAction(shellHolderId, isNavigate); - } else { - Log.w(TAG, "setFlutterNavigationAction is detached !"); - } - } - - /** - * Sets the D-VSync switch state. - * @param isEnable - Whether to enable D-VSync - */ - SetDVsyncSwitch(isEnable: boolean): void { - flutter.nativeSetDVsyncSwitch(this.nativeShellHolderId!, isEnable); - } - - /** - * Sends screen scrolling velocity to the native engine. - * @param type - The animation type - * @param velocity - The current screen scrolling velocity - */ - static animationVoting(type: number, velocity: number): void { - flutter.nativeAnimationVoting(type, velocity); - } - - /** - * Sends video frame count to the native engine. - * @param seconds - The time duration in seconds - * @param frameCount - The number of frames within the specified time duration - */ - static videoVoting(seconds: number, frameCount: number): void { - flutter.nativeVideoVoting(seconds, frameCount); - } - - /** - * Prefetches the frame rate configuration file. - */ - static prefetchFramesCfg(): void { - flutter.nativePrefetchFramesCfg(); - } - - /** - * Checks the LTPO (Low Temperature Polycrystalline Oxide) switch state. - * @returns The LTPO switch state value - */ - static checkLTPOSwitchState(): number { - return flutter.nativeCheckLTPOSwitchState(); - } - - /** - * Sets the QoS (Quality of Service) level when low memory is detected. - * @param lowMemoryLevel - The low memory level - */ - SetQosOnLowMemory(lowMemoryLevel: number): void { - flutter.nativeSetQosOnLowMemory(this.nativeShellHolderId!, lowMemoryLevel); - } - - SetAnimationStatus(animationStatus: number): void { - flutter.nativeSetAnimationStatus(this.nativeShellHolderId!, animationStatus); - } - - NotifyPageChanged(pageName: string, pageNameLen: number, windowID: number): number { - return flutter.nativeNotifyPageChanged(pageName, pageNameLen, windowID); - } -} - -/** - * Interface for handling accessibility state changes. - * Implementations of this interface will be notified when the accessibility state changes. - */ -export interface AccessibilityDelegate { - /** - * Called when the accessibility state changes. - * @param state - Whether accessibility is enabled - */ - accessibilityStateChange(state: Boolean): void; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterOverlaySurface.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterOverlaySurface.ets deleted file mode 100644 index 5ca47e3..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterOverlaySurface.ets +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterOverlaySurface.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * Represents an overlay surface in the Flutter rendering system. - * Overlay surfaces are used for displaying content above the main Flutter view. - */ -export class FlutterOverlaySurface { - private id: number; - - /** - * Constructs a new FlutterOverlaySurface instance. - * @param id - The unique identifier for this overlay surface - */ - constructor(id: number) { - this.id = id - } - - /** - * Gets the unique identifier of this overlay surface. - * @returns The overlay surface ID - */ - getId(): number { - return this.id; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterShellArgs.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterShellArgs.ets deleted file mode 100644 index 9cf6b65..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterShellArgs.ets +++ /dev/null @@ -1,163 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterShellArgs.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Want from '@ohos.app.ability.Want'; - -/** - * Encapsulates arguments for the Flutter shell. - * This class provides methods to parse and manage command-line arguments - * that are passed to the Flutter engine during initialization. - */ -export default class FlutterShellArgs { - static ARG_KEY_TRACE_STARTUP = "trace-startup"; - static ARG_TRACE_STARTUP = "--trace-startup"; - static ARG_KEY_START_PAUSED = "start-paused"; - static ARG_START_PAUSED = "--start-paused"; - static ARG_KEY_DISABLE_SERVICE_AUTH_CODES = "disable-service-auth-codes"; - static ARG_DISABLE_SERVICE_AUTH_CODES = "--disable-service-auth-codes"; - static ARG_KEY_ENDLESS_TRACE_BUFFER = "endless-trace-buffer"; - static ARG_ENDLESS_TRACE_BUFFER = "--endless-trace-buffer"; - static ARG_KEY_USE_TEST_FONTS = "use-test-fonts"; - static ARG_USE_TEST_FONTS = "--use-test-fonts"; - static ARG_KEY_ENABLE_DART_PROFILING = "enable-dart-profiling"; - static ARG_ENABLE_DART_PROFILING = "--enable-dart-profiling"; - static ARG_KEY_ENABLE_SOFTWARE_RENDERING = "enable-software-rendering"; - static ARG_ENABLE_SOFTWARE_RENDERING = "--enable-software-rendering"; - static ARG_KEY_SKIA_DETERMINISTIC_RENDERING = "skia-deterministic-rendering"; - static ARG_SKIA_DETERMINISTIC_RENDERING = "--skia-deterministic-rendering"; - static ARG_KEY_TRACE_SKIA = "trace-skia"; - static ARG_TRACE_SKIA = "--trace-skia"; - static ARG_KEY_TRACE_SKIA_ALLOWLIST = "trace-skia-allowlist"; - static ARG_TRACE_SKIA_ALLOWLIST = "--trace-skia-allowlist="; - static ARG_KEY_TRACE_SYSTRACE = "trace-systrace"; - static ARG_TRACE_SYSTRACE = "--trace-systrace"; - static ARG_KEY_ENABLE_IMPELLER = "enable-impeller"; - static ARG_ENABLE_IMPELLER = "--enable-impeller"; - static ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION = - "dump-skp-on-shader-compilation"; - static ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION = - "--dump-skp-on-shader-compilation"; - static ARG_KEY_CACHE_SKSL = "cache-sksl"; - static ARG_CACHE_SKSL = "--cache-sksl"; - static ARG_KEY_PURGE_PERSISTENT_CACHE = "purge-persistent-cache"; - static ARG_PURGE_PERSISTENT_CACHE = "--purge-persistent-cache"; - static ARG_KEY_VERBOSE_LOGGING = "verbose-logging"; - static ARG_VERBOSE_LOGGING = "--verbose-logging"; - static ARG_KEY_OBSERVATORY_PORT = "observatory-port"; - static ARG_OBSERVATORY_PORT = "--observatory-port="; - static ARG_KEY_DART_FLAGS = "dart-flags"; - static ARG_DART_FLAGS = "--dart-flags="; - static ARG_KEY_MSAA_SAMPLES = "msaa-samples"; - static ARG_MSAA_SAMPLES = "--msaa-samples="; - - /** - * Parses arguments from a Want object and creates a FlutterShellArgs instance. - * @param want - The Want object containing the parameters - * @returns A FlutterShellArgs instance with parsed arguments - */ - static fromWant(want: Want): FlutterShellArgs { - let flutterShellArgs: FlutterShellArgs = new FlutterShellArgs(); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_TRACE_STARTUP, FlutterShellArgs.ARG_TRACE_STARTUP, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_START_PAUSED, FlutterShellArgs.ARG_START_PAUSED, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_DISABLE_SERVICE_AUTH_CODES, - FlutterShellArgs.ARG_DISABLE_SERVICE_AUTH_CODES, want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_ENDLESS_TRACE_BUFFER, FlutterShellArgs.ARG_ENDLESS_TRACE_BUFFER, - want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_USE_TEST_FONTS, FlutterShellArgs.ARG_USE_TEST_FONTS, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_ENABLE_DART_PROFILING, - FlutterShellArgs.ARG_ENABLE_DART_PROFILING, want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, - FlutterShellArgs.ARG_ENABLE_SOFTWARE_RENDERING, want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_SKIA_DETERMINISTIC_RENDERING, - FlutterShellArgs.ARG_SKIA_DETERMINISTIC_RENDERING, want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_TRACE_SKIA, FlutterShellArgs.ARG_TRACE_SKIA, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_TRACE_SYSTRACE, FlutterShellArgs.ARG_TRACE_SYSTRACE, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_ENABLE_IMPELLER, FlutterShellArgs.ARG_ENABLE_IMPELLER, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_DUMP_SHADER_SKP_ON_SHADER_COMPILATION, - FlutterShellArgs.ARG_DUMP_SHADER_SKP_ON_SHADER_COMPILATION, want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_CACHE_SKSL, FlutterShellArgs.ARG_CACHE_SKSL, want, - flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_PURGE_PERSISTENT_CACHE, - FlutterShellArgs.ARG_PURGE_PERSISTENT_CACHE, want, flutterShellArgs); - FlutterShellArgs.checkArg(FlutterShellArgs.ARG_KEY_VERBOSE_LOGGING, FlutterShellArgs.ARG_VERBOSE_LOGGING, want, - flutterShellArgs); - - let skia_allow_list: Object = want.parameters![FlutterShellArgs.ARG_KEY_TRACE_SKIA_ALLOWLIST]; - if (skia_allow_list != undefined) { - flutterShellArgs.add(FlutterShellArgs.ARG_TRACE_SKIA_ALLOWLIST + (skia_allow_list as string)); - } - - let observatory_port: Object = want.parameters![FlutterShellArgs.ARG_KEY_OBSERVATORY_PORT]; - if (observatory_port != undefined && (observatory_port as number > 0)) { - flutterShellArgs.add(FlutterShellArgs.ARG_OBSERVATORY_PORT + (observatory_port as number)); - } - - let msaa: Object = want.parameters![FlutterShellArgs.ARG_KEY_MSAA_SAMPLES]; - if (msaa != undefined && (msaa as number > 1)) { - flutterShellArgs.add(FlutterShellArgs.ARG_MSAA_SAMPLES + (msaa as number)); - } - - let dart_flags: Object = want.parameters![FlutterShellArgs.ARG_KEY_DART_FLAGS]; - if (dart_flags != undefined) { - flutterShellArgs.add(FlutterShellArgs.ARG_DART_FLAGS + (msaa as string)); - } - return flutterShellArgs; - } - - /** - * Checks if an argument exists in the Want parameters and adds it to FlutterShellArgs if present. - * @param argKey - The key to look for in the Want parameters - * @param argFlag - The command-line flag to add if the argument is present - * @param want - The Want object containing the parameters - * @param flutterShellArgs - The FlutterShellArgs instance to add the flag to - */ - static checkArg(argKey: string, argFlag: string, want: Want, flutterShellArgs: FlutterShellArgs) { - if (want.parameters == undefined) { - return; - } - let value: Object = want.parameters![argKey]; - if (value != undefined && value as Boolean) { - flutterShellArgs.add(argFlag); - } - } - - /** Command-line arguments for the Flutter shell. */ - args: Set = new Set(); - - /** - * Adds an argument to the set of shell arguments. - * @param arg - The argument string to add - */ - add(arg: string) { - this.args.add(arg); - } - - /** - * Removes an argument from the set of shell arguments. - * @param arg - The argument string to remove - */ - remove(arg: string) { - this.args.delete(arg); - } - - /** - * Converts the set of arguments to an array. - * @returns An array containing all shell arguments - */ - toArray(): Array { - return Array.from(this.args); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartExecutor.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartExecutor.ets deleted file mode 100644 index 89824a2..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartExecutor.ets +++ /dev/null @@ -1,426 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on DartExecutor.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import resourceManager from '@ohos.resourceManager'; -import FlutterInjector from '../../../FlutterInjector'; -import { BinaryMessageHandler, BinaryReply, TaskQueue, TaskQueueOptions } from '../../../plugin/common/BinaryMessenger'; -import { BinaryMessenger } from '../../../plugin/common/BinaryMessenger'; -import StringCodec from '../../../plugin/common/StringCodec'; -import Log from '../../../util/Log'; -import { TraceSection } from '../../../util/TraceSection'; -import { FlutterCallbackInformation } from '../../../view/FlutterCallbackInformation'; -import FlutterNapi from '../FlutterNapi'; -import { DartMessenger } from './DartMessenger'; -import SendableBinaryMessageHandler from '../../../plugin/common/SendableBinaryMessageHandler' - - -const TAG = "DartExecutor"; - -/** - * Configures, bootstraps, and starts executing Dart code. - * - * To specify a top-level Dart function to execute, use a {@link DartEntrypoint} to tell - * DartExecutor where to find the Dart code to execute, and which Dart function to use as the - * entrypoint. To execute the entrypoint, pass the {@link DartEntrypoint} to - * {@link executeDartEntrypoint}. - * - * To specify a Dart callback to execute, use a {@link DartCallback}. A given Dart callback must - * be registered with the Dart VM to be invoked by a DartExecutor. To execute the callback, - * pass the {@link DartCallback} to {@link executeDartCallback}. - * - * Once started, a DartExecutor cannot be stopped. The associated Dart code will execute - * until it completes, or until the {@link FlutterEngine} that owns this - * DartExecutor is destroyed. - */ -export default class DartExecutor implements BinaryMessenger { - /** The FlutterNapi instance for native communication. */ - flutterNapi: FlutterNapi; - /** The resource manager for accessing application assets. */ - assetManager: resourceManager.ResourceManager; - private dartMessenger: DartMessenger; - private binaryMessenger: BinaryMessenger; - private isApplicationRunning: boolean = false; - private isolateServiceId: String = ""; - private isolateServiceIdListener: IsolateServiceIdListener | null = null; - private isolateChannelMessageHandler: BinaryMessageHandler = - new IsolateChannelMessageHandler(this.isolateServiceId, this.isolateServiceIdListener); - - /** - * Constructs a new DartExecutor instance. - * @param flutterNapi - The FlutterNapi instance for native communication - * @param assetManager - The resource manager for accessing assets - */ - constructor(flutterNapi: FlutterNapi, assetManager: resourceManager.ResourceManager) { - this.flutterNapi = flutterNapi; - this.assetManager = assetManager; - this.dartMessenger = new DartMessenger(flutterNapi); - this.dartMessenger.setMessageHandler("flutter/isolate", this.isolateChannelMessageHandler); - this.binaryMessenger = new DefaultBinaryMessenger(this.dartMessenger); - // The NAPI might already be attached if coming from a spawned engine. If so, correctly report - // that this DartExecutor is already running. - if (flutterNapi.isRunningDart) { - this.isApplicationRunning = true; - } - } - - /** - * Invoked when the FlutterEngine that owns this DartExecutor attaches to NAPI. - * - * When attached to NAPI, this DartExecutor begins handling 2-way communication to/from - * the Dart execution context. This communication is facilitated via 2 APIs: - * BinaryMessenger, which sends messages to Dart - * PlatformMessageHandler, which receives messages from Dart - */ - onAttachedToNAPI(): void { - Log.d(TAG, "Attached to NAPI. Registering the platform message handler for this Dart execution context."); - this.flutterNapi.setPlatformMessageHandler(this.dartMessenger); - } - - /** - * Invoked when the FlutterEngine that owns this DartExecutor detaches from NAPI. - * - * When detached from NAPI, this DartExecutor stops handling 2-way communication to/from - * the Dart execution context. - */ - onDetachedFromNAPI(): void { - Log.d(TAG, "Detached from NAPI. De-registering the platform message handler for this Dart execution context."); - this.flutterNapi.setPlatformMessageHandler(null); - } - - /** - * Checks if this DartExecutor is currently executing Dart code. - * - * @returns True if Dart code is being executed, false otherwise - */ - isExecutingDart(): boolean { - return this.isApplicationRunning; - } - - /** - * Starts executing Dart code based on the given dartEntrypoint and the dartEntrypointArgs. - * - * See DartEntrypoint for configuration options. - * - * @param dartEntrypoint - Specifies which Dart function to run, and where to find it - * @param dartEntrypointArgs - Arguments passed as a list of strings to Dart's entrypoint function - */ - executeDartEntrypoint(dartEntrypoint: DartEntrypoint, dartEntrypointArgs?: string[]): void { - if (this.isApplicationRunning) { - Log.w(TAG, "Attempted to run a DartExecutor that is already running."); - return; - } - - let traceId: number = TraceSection.begin("DartExecutor#executeDartEntrypoint"); - try { - Log.d(TAG, "Executing Dart entrypoint: " + dartEntrypoint); - this.flutterNapi.runBundleAndSnapshotFromLibrary( - dartEntrypoint.pathToBundle, - dartEntrypoint.dartEntrypointFunctionName, - dartEntrypoint.dartEntrypointLibrary, - this.assetManager, - dartEntrypointArgs ?? []); - - this.isApplicationRunning = true; - } finally { - TraceSection.endWithId("DartExecutor#executeDartEntrypoint", traceId); - } - } - - /** - * Starts executing Dart code based on the given dartCallback. - * - * See DartCallback for configuration options. - * - * @param dartCallback - Specifies which Dart callback to run, and where to find it - */ - executeDartCallback(dartCallback: DartCallback): void { - if (this.isApplicationRunning) { - Log.w(TAG, "Attempted to run a DartExecutor that is already running."); - return; - } - - let traceId: number = TraceSection.begin("DartExecutor#executeDartCallback"); - try { - Log.d(TAG, "Executing Dart callback: " + dartCallback); - this.flutterNapi.runBundleAndSnapshotFromLibrary( - dartCallback.pathToBundle, - dartCallback.callbackHandle.callbackName, - dartCallback.callbackHandle.callbackLibraryPath, - dartCallback.resourceManager, - []); - - this.isApplicationRunning = true; - } finally { - TraceSection.endWithId("DartExecutor#executeDartCallback", traceId); - } - } - - /** - * Gets a BinaryMessenger that can be used to send messages to, and receive messages - * from, Dart code that this DartExecutor is executing. - * @returns The BinaryMessenger instance - */ - getBinaryMessenger(): BinaryMessenger { - return this.binaryMessenger; - } - - /** - * Creates a background task queue for handling messages asynchronously. - * @param options - Optional task queue configuration options - * @returns A TaskQueue instance for background message processing - */ - makeBackgroundTaskQueue(options?: TaskQueueOptions): TaskQueue { - return this.getBinaryMessenger().makeBackgroundTaskQueue(options); - } - - - /** - * Sends a binary message to Dart over the specified channel. - * @param channel - The channel name for the message - * @param message - The message data as an ArrayBuffer - * @param callback - Optional callback to receive the reply from Dart - */ - send(channel: String, message: ArrayBuffer, callback?: BinaryReply): void { - this.getBinaryMessenger().send(channel, message, callback); - } - - /** - * Sets a message handler for incoming messages from Dart on the specified channel. - * @param channel - The channel name to listen on - * @param handler - The message handler, or null to remove the handler - * @param taskQueue - Optional task queue for processing messages - * @param args - Additional arguments to pass to the handler - */ - setMessageHandler(channel: String, handler: BinaryMessageHandler | SendableBinaryMessageHandler | null, - taskQueue?: TaskQueue, ...args: Object[]): void { - this.getBinaryMessenger().setMessageHandler(channel, handler, taskQueue, ...args); - } - - /** - * Gets the number of pending channel callback replies. - * When sending messages with reply callbacks, this tracks how many are still waiting for responses. - * Must be called from the main thread. - * Mainly useful for testing frameworks to determine if the app is idle. - * @returns The number of pending channel callback replies - */ - getPendingChannelResponseCount(): number { - return this.dartMessenger.getPendingChannelResponseCount(); - } - - /** - * Gets an identifier for this executor's primary isolate. This identifier can be used in - * queries to the Dart service protocol. - * @returns The isolate service ID - */ - getIsolateServiceId(): String { - return this.isolateServiceId; - } - - - /** - * Sets a listener that will be notified when an isolate identifier is available for this - * executor's primary isolate. - * @param listener - The listener to be notified when the isolate service ID is available - */ - setIsolateServiceIdListener(listener: IsolateServiceIdListener): void { - this.isolateServiceIdListener = listener; - if (this.isolateServiceIdListener != null && this.isolateServiceId != null) { - this.isolateServiceIdListener.onIsolateServiceIdAvailable(this.isolateServiceId); - } - } - - /** - * Notifies the Dart VM of a low memory event. - * This allows the Dart VM to free resources, but does not notify the Flutter application. - * To notify the Flutter application, use SystemChannel.sendMemoryPressureWarning(). - * - * Note: Calling this method may cause performance issues. Avoid calling during startup or animations. - */ - notifyLowMemoryWarning(): void { - if (this.flutterNapi.isAttached()) { - this.flutterNapi.notifyLowMemoryWarning(); - } - } -} - - -/** - * Configuration options that specify which Dart entrypoint function is executed and where to find - * that entrypoint and other assets required for Dart execution. - */ -export class DartEntrypoint { - /** The path within the ResourceManager where the app will look for assets. */ - pathToBundle: string; - /** The library or file location that contains the Dart entrypoint function. */ - dartEntrypointLibrary: string; - /** The name of a Dart function to execute. */ - dartEntrypointFunctionName: string; - - /** - * Constructs a new DartEntrypoint instance. - * @param pathToBundle - The path within the AssetManager where the app will look for assets - * @param dartEntrypointLibrary - The library or file location that contains the Dart entrypoint function - * @param dartEntrypointFunctionName - The name of a Dart function to execute - */ - constructor(pathToBundle: string, - dartEntrypointLibrary: string, - dartEntrypointFunctionName: string) { - this.pathToBundle = pathToBundle; - this.dartEntrypointLibrary = dartEntrypointLibrary; - this.dartEntrypointFunctionName = dartEntrypointFunctionName; - } - - /** - * Creates a default DartEntrypoint using the main function. - * @returns A DartEntrypoint configured with the default main entrypoint - * @throws Error if FlutterLoader is not initialized - */ - static createDefault() { - const flutterLoader = FlutterInjector.getInstance().getFlutterLoader(); - if (!flutterLoader.initialized) { - throw new Error( - "DartEntrypoints can only be created once a FlutterEngine is created."); - } - return new DartEntrypoint(flutterLoader.findAppBundlePath(), "", "main"); - } -} - - -/** - * Callback interface invoked when the isolate identifier becomes available. - */ -interface IsolateServiceIdListener { - /** - * Called when the isolate service ID becomes available. - * @param isolateServiceId - The isolate service ID - */ - onIsolateServiceIdAvailable(isolateServiceId: String): void; -} - - -/** - * Configuration options that specify which Dart callback function is executed and where to find - * that callback and other assets required for Dart execution. - */ -export class DartCallback { - /** Standard OpenHarmony ResourceManager for accessing assets. */ - public resourceManager: resourceManager.ResourceManager; - /** The path within the ResourceManager where the app will look for assets. */ - public pathToBundle: string; - /** A Dart callback that was previously registered with the Dart VM. */ - public callbackHandle: FlutterCallbackInformation; - - /** - * Constructs a new DartCallback instance. - * @param resourceManager - Standard OpenHarmony ResourceManager - * @param pathToBundle - The path within the ResourceManager where the app will look for assets - * @param callbackHandle - A Dart callback that was previously registered with the Dart VM - */ - constructor(resourceManager: resourceManager.ResourceManager, - pathToBundle: string, - callbackHandle: FlutterCallbackInformation) { - this.resourceManager = resourceManager; - this.pathToBundle = pathToBundle; - this.callbackHandle = callbackHandle; - } - - /** - * Returns a string representation of this DartCallback. - * @returns A string describing the callback's bundle path, library path, and function name - */ - toString(): String { - return "DartCallback( bundle path: " - + this.pathToBundle - + ", library path: " - + this.callbackHandle.callbackLibraryPath - + ", function: " - + this.callbackHandle.callbackName - + " )"; - } -} - -/** - * Default implementation of BinaryMessenger that delegates to DartMessenger. - */ -export class DefaultBinaryMessenger implements BinaryMessenger { - private messenger: DartMessenger; - - /** - * Constructs a new DefaultBinaryMessenger instance. - * @param messenger - The DartMessenger instance to delegate to - */ - constructor(messenger: DartMessenger) { - this.messenger = messenger; - } - - /** - * Creates a background task queue for handling messages asynchronously. - * @param options - Optional task queue configuration options - * @returns A TaskQueue instance for background message processing - */ - makeBackgroundTaskQueue(options?: TaskQueueOptions): TaskQueue { - return this.messenger.makeBackgroundTaskQueue(options); - } - - /** - * Sends a message from OpenHarmony to Dart over the given channel and then - * has the provided callback invoked when the Dart side responds. - * - * @param channel - The name of the logical channel used for the message - * @param message - The message payload as an ArrayBuffer, or null for an empty message - * @param callback - A callback invoked when the Dart application responds to the message - */ - send(channel: String, message: ArrayBuffer, callback?: BinaryReply): void { - this.messenger.send(channel, message, callback); - } - - /** - * Sets the given BinaryMessageHandler as the singular handler for all incoming messages - * received from the Dart side of this Dart execution context. - * - * @param channel - The name of the channel - * @param handler - A BinaryMessageHandler to be invoked on incoming messages, or null - * @param taskQueue - Optional task queue for processing messages - * @param args - Additional arguments to pass to the handler - */ - setMessageHandler(channel: String, handler: BinaryMessageHandler | SendableBinaryMessageHandler | null, - taskQueue?: TaskQueue, ...args: Object[]): void { - this.messenger.setMessageHandler(channel, handler, taskQueue, ...args); - } -} - -/** - * Message handler for the isolate channel that receives isolate service IDs. - */ -class IsolateChannelMessageHandler implements BinaryMessageHandler { - private isolateServiceId: String; - private isolateServiceIdListener: IsolateServiceIdListener | null = null; - - /** - * Constructs a new IsolateChannelMessageHandler instance. - * @param isolateServiceId - The isolate service ID - * @param isolateServiceIdListener - Optional listener to be notified when the ID is available - */ - constructor(isolateServiceId: String, isolateServiceIdListener: IsolateServiceIdListener | null) { - this.isolateServiceId = isolateServiceId; - this.isolateServiceIdListener = isolateServiceIdListener; - } - - /** - * Handles a message received on the isolate channel. - * @param message - The message containing the isolate service ID - * @param callback - The reply callback - */ - onMessage(message: ArrayBuffer, callback: BinaryReply): void { - this.isolateServiceId = StringCodec.INSTANCE.decodeMessage(message); - if (this.isolateServiceIdListener != null) { - this.isolateServiceIdListener.onIsolateServiceIdAvailable(this.isolateServiceId); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartMessenger.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartMessenger.ets deleted file mode 100644 index e85ef1c..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartMessenger.ets +++ /dev/null @@ -1,516 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on DartMessenger.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import { ErrorEvent, Queue, taskpool, worker, MessageEvents, JSON } from '@kit.ArkTS'; - -import Log from '../../../util/Log'; -import { - BinaryMessageHandler, - BinaryMessenger, - BinaryReply, - TaskPriority, - TaskQueue, - TaskQueueOptions -} from '../../../plugin/common/BinaryMessenger'; -import FlutterNapi from '../FlutterNapi'; -import { PlatformMessageHandler } from './PlatformMessageHandler'; -import { TraceSection } from '../../../util/TraceSection'; -import SendableBinaryMessageHandler from '../../../plugin/common/SendableBinaryMessageHandler' - -/** - * Message conduit for 2-way communication between OpenHarmony and Dart. - * - * See {@link BinaryMessenger}, which sends messages from OpenHarmony to Dart - * - * See {@link PlatformMessageHandler}, which handles messages to OpenHarmony from Dart - */ - -const TAG = "DartMessenger"; - -export class DartMessenger implements BinaryMessenger, PlatformMessageHandler { - /** The FlutterNapi instance for native communication. */ - flutterNapi: FlutterNapi; - /** Map of channel names to their message handlers. */ - messageHandlers: Map = new Map(); - /** Map of reply IDs to their callback functions waiting for responses. */ - pendingReplies: Map = new Map(); - /** The next reply ID to use for message callbacks. */ - nextReplyId: number = 1; - /** Factory for creating task queues for background message processing. */ - taskQueueFactory: TaskQueueFactory; - /** Map of TaskQueue tokens to their actual task queue implementations. */ - createdTaskQueues: Map = new Map(); - - /** - * Constructs a new DartMessenger instance. - * @param flutterNapi - The FlutterNapi instance for native communication - */ - constructor(flutterNapi: FlutterNapi) { - this.flutterNapi = flutterNapi; - this.taskQueueFactory = new DefaultTaskQueueFactory(); - } - - /** - * Creates a background task queue for handling messages asynchronously. - * @param options - Optional task queue configuration options - * @returns A TaskQueue instance for background message processing - */ - makeBackgroundTaskQueue(options?: TaskQueueOptions): TaskQueue { - let taskQueue: DartMessengerTaskQueue = - this.taskQueueFactory.makeBackgroundTaskQueue(options ?? new TaskQueueOptions()); - let token: TaskQueueToken = new TaskQueueToken(); - this.createdTaskQueues.set(token, taskQueue); - return token; - } - - /** - * Sets a message handler for incoming messages from Dart on the specified channel. - * @param channel - The channel name to listen on - * @param handler - The message handler, or null to remove the handler - * @param taskQueue - Optional task queue for processing messages - * @param args - Additional arguments to pass to the handler - */ - setMessageHandler(channel: String, handler: BinaryMessageHandler | SendableBinaryMessageHandler | null, - taskQueue?: TaskQueue, ...args: Object[]): void { - if (handler == null) { - Log.d(TAG, "Removing handler for channel '" + channel + "'"); - this.messageHandlers.delete(channel); - return; - } - let dartMessengerTaskQueue: DartMessengerTaskQueue | null = null; - if (taskQueue !== null && taskQueue !== undefined) { - dartMessengerTaskQueue = this.createdTaskQueues.get(taskQueue) ?? null; - if (dartMessengerTaskQueue == null) { - throw new Error( - "Unrecognized TaskQueue, use BinaryMessenger to create your TaskQueue (ex makeBackgroundTaskQueue)." - ); - } - } - Log.d(TAG, "Setting handler for channel '" + channel + "'"); - - this.messageHandlers.set(channel, new HandlerInfo(handler, dartMessengerTaskQueue, ...args)); - } - - /** - * Sends a binary message to Dart over the specified channel. - * @param channel - The channel name for the message - * @param message - The message data as an ArrayBuffer, or null for an empty message - * @param callback - Optional callback to receive the reply from Dart - */ - send(channel: String, message: ArrayBuffer, callback?: BinaryReply): void { - Log.d(TAG, "Sending message over channel '" + channel + "'"); - let traceId: number = TraceSection.begin("DartMessenger#send on " + channel); - try { - Log.d(TAG, "Sending message with callback over channel '" + channel + "'"); - let replyId: number = this.nextReplyId++; - if (callback != null) { - this.pendingReplies.set(replyId, callback); - } - if (message == null) { - this.flutterNapi.dispatchEmptyPlatformMessage(channel, replyId); - } else { - this.flutterNapi.dispatchPlatformMessage(channel, message, message.byteLength, replyId); - } - } finally { - TraceSection.endWithId("DartMessenger#send on " + channel, traceId); - } - this.IsFlutterNavigationExecuted(channel); - } - - /** - * Dispatches a message to a task queue for asynchronous processing. - * @param handlerInfo - The handler information containing the handler and task queue - * @param message - The message data as an ArrayBuffer - * @param replyId - The reply ID for responding to the message - */ - dispatchMessageToQueue(handlerInfo: HandlerInfo, message: ArrayBuffer, replyId: number): void { - let taskState: TaskState = new TaskState(handlerInfo.handler as ESObject, message, ...handlerInfo.args); - handlerInfo.taskQueue?.dispatch(taskState, new Reply(this.flutterNapi, replyId)); - } - - /** - * Invokes a message handler synchronously. - * @param handler - The message handler to invoke, or null if no handler is registered - * @param message - The message data as an ArrayBuffer - * @param replyId - The reply ID for responding to the message - */ - invokeHandler(handler: BinaryMessageHandler | null, message: ArrayBuffer, replyId: number): void { - if (handler != null) { - try { - Log.d(TAG, "Deferring to registered handler to process message."); - handler.onMessage(message, new Reply(this.flutterNapi, replyId)); - } catch (ex) { - Log.e(TAG, "Uncaught exception in binary message listener", ex); - this.flutterNapi.invokePlatformMessageEmptyResponseCallback(replyId); - } - } else { - Log.d(TAG, "No registered handler for message. Responding to Dart with empty reply message."); - this.flutterNapi.invokePlatformMessageEmptyResponseCallback(replyId); - } - } - - /** - * Handles a message received from Dart over a specific channel. - * @param channel - The channel name for the message - * @param message - The message data as an ArrayBuffer - * @param replyId - The reply ID for responding to the message - * @param messageData - Additional message data - */ - handleMessageFromDart(channel: String, message: ArrayBuffer, replyId: number, messageData: number): void { - Log.d(TAG, "Received message from Dart over channel '" + channel + "'"); - let handlerInfo: HandlerInfo | null = this.messageHandlers.get(channel) ?? null; - if (handlerInfo?.taskQueue != null) { - this.dispatchMessageToQueue(handlerInfo, message, replyId); - } else { - this.invokeHandler(handlerInfo?.handler as BinaryMessageHandler, message, replyId); - } - this.IsFlutterNavigationExecuted(channel); - } - - /** - * Handles a platform message response from Dart. - * @param replyId - The reply ID that was sent with the original message - * @param reply - The reply data as an ArrayBuffer - */ - handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void { - Log.d(TAG, "Received message reply from Dart."); - let callback: BinaryReply | null = this.pendingReplies.get(replyId) ?? null; - this.pendingReplies.delete(replyId); - if (callback != null) { - try { - Log.d(TAG, "Invoking registered callback for reply from Dart."); - callback.reply(reply); - } catch (e) { - Log.e(TAG, "Uncaught exception in binary message reply handler", e); - } - } - } - - /** - * Returns the number of pending channel callback replies. - * - * When sending messages to the Flutter application using BinaryMessenger.send, - * developers can optionally specify a reply callback if they expect a reply from the Flutter application. - * - * This method tracks all the pending callbacks that are waiting for response, and is supposed - * to be called from the main thread (as other methods). Calling from a different thread could - * possibly capture an indeterministic internal state, so don't do it. - * @returns The number of pending channel callback replies - */ - getPendingChannelResponseCount(): number { - return this.pendingReplies.size; - } - - /** - * Checks if the current Flutter page is performing navigation and notifies the native side. - * @param channel - The channel name to check - */ - IsFlutterNavigationExecuted(channel: String): void { - if (channel == "flutter/navigation") { - this.flutterNapi.setFlutterNavigationAction(this.flutterNapi.nativeShellHolderId!, true); - Log.d(TAG, "setFlutterNavigationAction -> '" + channel + "'"); - } - } -} - -/** - * Holds information about a platform handler, such as the task queue that processes messages from - * Dart. - */ -class HandlerInfo { - handler: BinaryMessageHandler | SendableBinaryMessageHandler; - taskQueue: DartMessengerTaskQueue | null; - args: Object[]; - - /** - * Constructs a new HandlerInfo instance. - * @param handler - The message handler - * @param taskQueue - The task queue for processing messages, or null for synchronous processing - * @param args - Additional arguments to pass to the handler - */ - constructor(handler: BinaryMessageHandler | SendableBinaryMessageHandler, - taskQueue: DartMessengerTaskQueue | null, - ...args: Object[]) { - this.handler = handler; - this.taskQueue = taskQueue; - this.args = args; - } -} - -/** - * Implementation of BinaryReply that sends replies back to Dart. - */ -class Reply implements BinaryReply { - flutterNapi: FlutterNapi; - replyId: number; - done: boolean = false; - - /** - * Constructs a new Reply instance. - * @param flutterNapi - The FlutterNapi instance for sending replies - * @param replyId - The reply ID for this reply - */ - constructor(flutterNapi: FlutterNapi, replyId: number) { - this.flutterNapi = flutterNapi; - this.replyId = replyId; - } - - /** - * Sends a reply back to Dart. - * @param reply - The reply data as an ArrayBuffer, or null for an empty reply - * @throws Error if reply has already been submitted - */ - reply(reply: ArrayBuffer | null) { - if (this.done) { - throw new Error("Reply already submitted"); - } - - if (reply == null) { - this.flutterNapi.invokePlatformMessageEmptyResponseCallback(this.replyId); - } else { - this.flutterNapi.invokePlatformMessageResponseCallback(this.replyId, reply, reply.byteLength); - } - } -} - -/** - * Represents the state of a task to be executed in a background task queue. - */ -export class TaskState { - /** The message handler to execute. */ - handler: SendableBinaryMessageHandler; - /** The message data as an ArrayBuffer. */ - message: ArrayBuffer; - /** Additional arguments to pass to the handler. */ - args: Object[]; - - /** - * Constructs a new TaskState instance. - * @param handler - The message handler to execute - * @param message - The message data as an ArrayBuffer - * @param args - Additional arguments to pass to the handler - */ - constructor(handler: SendableBinaryMessageHandler, message: ArrayBuffer, ...args: Object[]) { - this.handler = handler; - this.message = message; - this.args = args; - } -} - -/** - * Interface for task queues that process messages asynchronously. - */ -interface DartMessengerTaskQueue { - /** - * Dispatches a task to be executed in the task queue. - * @param taskState - The task state containing the handler and message - * @param callback - The reply callback for sending responses - */ - dispatch(taskState: TaskState, callback: Reply): void; -} - -/** - * Interface for serial task queues that process messages sequentially. - */ -interface SerialTaskQueue extends DartMessengerTaskQueue { -} - -/** - * Factory interface for creating task queues. - */ -interface TaskQueueFactory { - /** - * Creates a background task queue with the specified options. - * @param options - Task queue configuration options - * @returns A DartMessengerTaskQueue instance - */ - makeBackgroundTaskQueue(options: TaskQueueOptions): DartMessengerTaskQueue; -} - -/** - * Task queue that processes messages concurrently. - */ -class ConcurrentTaskQueue implements DartMessengerTaskQueue { - private priority: TaskPriority; - - /** - * Constructs a new ConcurrentTaskQueue instance. - * @param priority - The priority level for task execution - */ - constructor(priority: TaskPriority) { - this.priority = priority; - } - - /** - * Dispatches a task to be executed concurrently. - * @param taskState - The task state containing the handler and message - * @param callback - The reply callback for sending responses - */ - dispatch(taskState: TaskState, callback: Reply): void { - let task: taskpool.Task = new taskpool.Task(handleMessageInBackground, - taskState.handler, - taskState.message, - ...taskState.args); - taskpool.execute(task, this.priority as number).then((result: Object) => { - callback.reply(result as ArrayBuffer); - }).catch((err: string) => { - callback.reply(null); - Log.e(TAG, "Oops! Failed to execute task: ", err); - }); - } -} - -const scriptURL: string = '../workers/PlatformChannelWorker.ets'; -/** - * Task queue that processes messages serially using a worker thread. - */ -class SerialTaskQueueWithWorker implements SerialTaskQueue { - private static workerInstance: worker.ThreadWorker | null = null; - - /** - * Constructs a new SerialTaskQueueWithWorker instance. - * Creates a singleton worker instance if one doesn't exist. - */ - constructor () { - if (!SerialTaskQueueWithWorker.workerInstance) { - SerialTaskQueueWithWorker.workerInstance = - new worker.ThreadWorker(scriptURL, {name: 'PlatformChannelWorker'}); - } - } - - /** - * Dispatches a task to be executed serially in the worker thread. - * @param taskState - The task state containing the handler and message - * @param callback - The reply callback for sending responses - */ - dispatch(taskState: TaskState, callback: Reply): void { - SerialTaskQueueWithWorker.workerInstance!.onmessage = (e: MessageEvents): void => { - callback.reply(e.data as ArrayBuffer); - } - - SerialTaskQueueWithWorker.workerInstance!.onerror = (err: ErrorEvent) => { - callback.reply(null); - Log.e(TAG, "Oops! Failed to execute task in worker thread: ", err.message); - } - - SerialTaskQueueWithWorker.workerInstance!.postMessageWithSharedSendable(taskState, [taskState.message]); - } -} - -type Runnable = () => Promise; -/** - * Task queue that processes messages serially using a task pool. - */ -class SerialTaskQueueWithTaskPool implements SerialTaskQueue { - private priority: TaskPriority; - private queue: Queue = new Queue(); - private isRunning: boolean = false; - - /** - * Constructs a new SerialTaskQueueWithTaskPool instance. - * @param priority - The priority level for task execution - */ - constructor(priority: TaskPriority) { - this.priority = priority; - } - - /** - * Dispatches a task to be executed serially in the task pool. - * @param taskState - The task state containing the handler and message - * @param callback - The reply callback for sending responses - */ - dispatch(taskState: TaskState, callback: Reply): void { - let task: taskpool.Task = new taskpool.Task(handleMessageInBackground, - taskState.handler, - taskState.message, - ...taskState.args); - const runnable: Runnable = async () => { - try { - const result = await taskpool.execute(task, this.priority as number); - callback.reply(result as ArrayBuffer); - } catch (err) { - callback.reply(null); - Log.e(TAG, "Oops! Failed to execute task: ", err); - } - }; - - this.queue.add(runnable); - - if (!this.isRunning) { - this.runNext(); - } - } - - private async runNext(): Promise { - if (this.queue.length > 0) { - this.isRunning = true; - const task = this.queue.pop(); - try { - await task(); - } finally { - this.isRunning = false; - this.runNext(); // 执行下一个任务 - } - } - } -} - -/** - * Default implementation of TaskQueueFactory. - */ -class DefaultTaskQueueFactory implements TaskQueueFactory { - /** - * Creates a background task queue based on the provided options. - * @param options - Task queue configuration options - * @returns A DartMessengerTaskQueue instance (serial or concurrent) - */ - makeBackgroundTaskQueue(options: TaskQueueOptions): DartMessengerTaskQueue { - if (options.isSingleThreadMode()) { - return new SerialTaskQueueWithWorker(); - } else { - if (options.getIsSerial()) { - return new SerialTaskQueueWithTaskPool(options.getPriority()); - } - return new ConcurrentTaskQueue(options.getPriority()); - } - } -} - -/** - * Token implementation of TaskQueue used to identify task queues. - */ -class TaskQueueToken implements TaskQueue { -} - -/** - * Handles a message in the background thread. - * This function is executed concurrently and processes messages asynchronously. - * @param handler - The message handler to execute - * @param message - The message data as an ArrayBuffer - * @param args - Additional arguments to pass to the handler - * @returns A promise that resolves to the reply ArrayBuffer, or null if no reply - */ -@Concurrent -async function handleMessageInBackground(handler: SendableBinaryMessageHandler, - message: ArrayBuffer, - ...args: Object[]): Promise { - const result = await new Promise((resolve, reject) => { - try { - handler.onMessage(message, { - reply: (reply: ArrayBuffer | null): void => { - resolve(reply); - } - }, ...args); - } catch (e) { - reject(null); - Log.e('WARNING', "Oops! Failed to handle message in the background: ", e); - } - }); - return result; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ets deleted file mode 100644 index 8d3cf70..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ets +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformMessageHandler.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * Interface for handling platform messages from Dart. - * This interface provides methods to receive messages from the Dart side of the Flutter application. - */ -export interface PlatformMessageHandler { - - /** - * Handles a message received from Dart over a specific channel. - * @param channel - The channel name for the message - * @param message - The message data as an ArrayBuffer - * @param replyId - The reply ID for responding to the message - * @param messageData - Additional message data - */ - handleMessageFromDart(channel: String, message: ArrayBuffer, replyId: number, messageData: number): void; - - /** - * Handles a platform message response from Dart. - * @param replyId - The reply ID that was sent with the original message - * @param reply - The reply data as an ArrayBuffer - */ - handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void; - -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ets deleted file mode 100644 index 54e1d44..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ets +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import FlutterApplicationInfo from './FlutterApplicationInfo'; -import common from '@ohos.app.ability.common'; - -/** - * Loader for Flutter application information. - * This class provides a static method to load FlutterApplicationInfo from the application context. - */ -export default class ApplicationInfoLoader { - /** - * Loads FlutterApplicationInfo from the application context. - * @param context - The application context - * @returns A FlutterApplicationInfo instance with default or context-based values - */ - static load(context: common.Context) { - let applicationInfo = - new FlutterApplicationInfo(null, null, null, null, null, context.bundleCodeDir + '/libs/arm64', true); - return applicationInfo - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ets deleted file mode 100644 index 1a3f9af..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ets +++ /dev/null @@ -1,71 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterApplicationInfo.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import BuildProfile from "../../../../../../BuildProfile"; - -const DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so"; -const DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data"; -const DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data"; -const DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets"; - - -/** - * Contains application information for Flutter initialization. - * This class holds configuration data such as AOT library names, snapshot data paths, - * asset directories, and build mode information. - */ -export default class FlutterApplicationInfo { - /** Name of the AOT shared library. */ - aotSharedLibraryName: string; - /** Name of the VM snapshot data file. */ - vmSnapshotData: string; - /** Name of the isolate snapshot data file. */ - isolateSnapshotData: string; - /** Directory containing Flutter assets. */ - flutterAssetsDir: string; - /** Domain network policy configuration. */ - domainNetworkPolicy: string; - /** Directory containing native libraries. */ - nativeLibraryDir: string; - /** Whether to automatically register plugins. */ - automaticallyRegisterPlugins: boolean; - /** Whether the application is running in debug mode. */ - isDebugMode: boolean; - /** Whether the application is running in profile mode. */ - isProfile: boolean; - - /** - * Constructs a new FlutterApplicationInfo instance. - * @param aotSharedLibraryName - Name of the AOT shared library, or null to use default - * @param vmSnapshotData - Name of the VM snapshot data file, or null to use default - * @param isolateSnapshotData - Name of the isolate snapshot data file, or null to use default - * @param flutterAssetsDir - Directory containing Flutter assets, or null to use default - * @param domainNetworkPolicy - Domain network policy, or null for empty string - * @param nativeLibraryDir - Directory containing native libraries - * @param automaticallyRegisterPlugins - Whether to automatically register plugins - */ - constructor(aotSharedLibraryName: string | null, - vmSnapshotData: string | null, - isolateSnapshotData: string | null, - flutterAssetsDir: string | null, - domainNetworkPolicy: string | null, - nativeLibraryDir: string, - automaticallyRegisterPlugins: boolean) { - this.aotSharedLibraryName = aotSharedLibraryName == null ? DEFAULT_AOT_SHARED_LIBRARY_NAME : aotSharedLibraryName; - this.vmSnapshotData = vmSnapshotData == null ? DEFAULT_VM_SNAPSHOT_DATA : vmSnapshotData; - this.isolateSnapshotData = isolateSnapshotData == null ? DEFAULT_ISOLATE_SNAPSHOT_DATA : isolateSnapshotData; - this.flutterAssetsDir = flutterAssetsDir == null ? DEFAULT_FLUTTER_ASSETS_DIR : flutterAssetsDir; - this.domainNetworkPolicy = domainNetworkPolicy == null ? "" : domainNetworkPolicy; - this.nativeLibraryDir = nativeLibraryDir; - this.automaticallyRegisterPlugins = automaticallyRegisterPlugins; - this.isDebugMode = "debug" == String(BuildProfile.BUILD_MODE_NAME); - this.isProfile = "profile" == String(BuildProfile.BUILD_MODE_NAME); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/FlutterLoader.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/FlutterLoader.ets deleted file mode 100644 index a0f37c7..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/loader/FlutterLoader.ets +++ /dev/null @@ -1,400 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterLoader.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * FlutterLoader is responsible for starting the Dart VM and loading Dart code. - * This class locates Flutter resources in the HAP package and loads the Flutter native library. - * It handles initialization, resource copying, and Dart VM configuration. - */ -import FlutterShellArgs from '../FlutterShellArgs'; -import FlutterNapi from '../FlutterNapi'; -import Log from '../../../util/Log'; -import FlutterApplicationInfo from './FlutterApplicationInfo'; -import common from '@ohos.app.ability.common'; -import StringUtils from '../../../util/StringUtils'; -import ApplicationInfoLoader from './ApplicationInfoLoader'; -import bundleManager from '@ohos.bundle.bundleManager'; -import fs from '@ohos.file.fs'; -import { BusinessError } from '@ohos.base'; -import data_preferences from '@ohos.data.preferences'; -import { util } from '@kit.ArkTS'; -import deviceInfo from '@ohos.deviceInfo'; -import { json5Tojson } from '../../../util/Json5ToJson'; - -const TAG = "FlutterLoader"; - -// Flutter engine shared library -const DEFAULT_LIBRARY = "libflutter.so"; -// Default kernel file for JIT builds -const DEFAULT_KERNEL_BLOB = "kernel_blob.bin"; -// Default snapshot library for JIT builds -const VMSERVICE_SNAPSHOT_LIBRARY = "libvmservice_snapshot.so"; -// Key for snapshot asset path -const SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path"; -// Key for VM snapshot data -const VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data"; -// Key for isolate snapshot data -const ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data"; - - -const AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name"; - -const AOT_VMSERVICE_SHARED_LIBRARY_NAME = "aot-vmservice-shared-library-name"; - -// File path separator -const FILE_SEPARATOR = "/"; - -const TIMESTAMP_PREFIX = "res_timestamp-"; - -const ENABLE_IMPELLER_TAG = "enable_impeller"; - -const TRUE_STRING = "true"; - -const BUILD_INFO_FILE_NAME = "buildinfo.json5"; - -/** - * Represents a string item with name and value. - */ -interface StringItem { - name: string; - value: string; -} - -/** - * Represents build information data containing an array of string items. - */ -interface InfoData { - string: StringItem[]; -} - -/** - * Prefetches the default font manager asynchronously. - * This should be called before using fonts in the Flutter engine. - */ -async function prefetchDefaultFontManager(): Promise { - await new Promise((resolve: Function) => { - FlutterNapi.prefetchDefaultFontManager() - resolve() - }) -} - -/** - * FlutterLoader is responsible for starting the Dart VM and loading Dart code. - * This class locates Flutter resources in the HAP package and loads the Flutter native library. - * It handles initialization, resource copying, and Dart VM configuration. - */ -export default class FlutterLoader { - /** The FlutterNapi instance for native communication. */ - flutterNapi: FlutterNapi; - /** Initialization result containing paths for app storage, engine caches, and data directory. */ - initResult: InitResult | null = null; - /** Flutter application information including asset paths and build mode. */ - flutterApplicationInfo: FlutterApplicationInfo | null = null; - /** The application context for accessing resources. */ - context: common.Context | null = null; - /** Whether the FlutterLoader has been initialized. */ - initialized: boolean = false; - /** Timestamp when initialization started. */ - initStartTimestampMillis: number = 0; - /** Whether Impeller rendering backend is enabled. */ - isEnableImpeller: boolean = false; - - /** - * Constructs a new FlutterLoader instance. - * @param flutterNapi - The FlutterNapi instance for native communication - */ - constructor(flutterNapi: FlutterNapi) { - this.flutterNapi = flutterNapi; - } - - /** - * Gets build information from the buildinfo.json5 file. - * @param context - The application context - * @returns A map containing build information key-value pairs - */ - private getBuildInfo(context: common.Context): Map { - let buildInfoMap: Map = new Map(); - try { - let rawFile = context.resourceManager.getRawFileContentSync(BUILD_INFO_FILE_NAME); - let textDecoder = util.TextDecoder.create('utf-8', { - ignoreBOM: true - }); - let record = textDecoder.decodeWithStream(rawFile, { - stream: false - }); - let jsonRecord: InfoData = JSON.parse(json5Tojson(record)); - jsonRecord.string.forEach((item: StringItem) => { - buildInfoMap.set(item.name, item.value); - }); - return buildInfoMap; - } catch (error) { - Log.e(TAG, "can not find buildinfo.json5 file.") - return buildInfoMap; - } - - } - - /** - * Starts initialization of the native system. - * - * This loads the Flutter engine's native library to enable subsequent NAPI calls. This also - * starts locating and unpacking Dart resources packaged in the app's HAP. - * - * Calling this method multiple times has no effect. - * - * @param context - The OpenHarmony application context - */ - startInitialization(context: common.Context) { - Log.d(TAG, "flutterLoader start init") - this.initStartTimestampMillis = Date.now(); - this.context = context; - this.flutterApplicationInfo = ApplicationInfoLoader.load(context); - prefetchDefaultFontManager(); - if (this.flutterApplicationInfo!.isDebugMode) { - this.copyResource(context) - } - let buildInfoMap = this.getBuildInfo(this.context!); - if (!buildInfoMap.has(ENABLE_IMPELLER_TAG) || buildInfoMap.get(ENABLE_IMPELLER_TAG) == TRUE_STRING) { - this.isEnableImpeller = true; - } else { - this.isEnableImpeller = false; - } - this.initResult = new InitResult( - `${context.filesDir}/`, - `${context.cacheDir}/`, - `${context.filesDir}` - ) - Log.d(TAG, "flutterLoader end init") - } - - private copyResource(context: common.Context) { - let filePath = context.filesDir + FILE_SEPARATOR + this.flutterApplicationInfo!.flutterAssetsDir - const timestamp = this.checkTimestamp(filePath); - if (timestamp == null) { - Log.d(TAG, "no need copyResource") - return; - } - if (this.context != null) { - Log.d(TAG, "start copyResource") - if (fs.accessSync(filePath + FILE_SEPARATOR + DEFAULT_KERNEL_BLOB)) { - Log.d(TAG, "hap has changed, start delete previous file") - fs.rmdirSync(filePath); - } - - if (!fs.accessSync(filePath)) { - fs.mkdirSync(filePath) - } - - let kernelBuffer = - this.context.resourceManager.getRawFileContentSync(this.flutterApplicationInfo!.flutterAssetsDir + - FILE_SEPARATOR + DEFAULT_KERNEL_BLOB) - let kernelFile = - fs.openSync(filePath + FILE_SEPARATOR + DEFAULT_KERNEL_BLOB, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) - fs.writeSync(kernelFile.fd, kernelBuffer.buffer) - - let vmBuffer = - this.context.resourceManager.getRawFileContentSync(this.flutterApplicationInfo!.flutterAssetsDir + - FILE_SEPARATOR + this.flutterApplicationInfo!.vmSnapshotData) - let vmFile = fs.openSync(filePath + FILE_SEPARATOR + this.flutterApplicationInfo!.vmSnapshotData, - fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) - fs.writeSync(vmFile.fd, vmBuffer.buffer) - - let isolateBuffer = - this.context.resourceManager.getRawFileContentSync(this.flutterApplicationInfo!.flutterAssetsDir + - FILE_SEPARATOR + this.flutterApplicationInfo!.isolateSnapshotData) - let isolateFile = fs.openSync(filePath + FILE_SEPARATOR + this.flutterApplicationInfo!.isolateSnapshotData, - fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) - fs.writeSync(isolateFile.fd, isolateBuffer.buffer) - - if (timestamp != null) { - fs.closeSync(fs.openSync(filePath + FILE_SEPARATOR + timestamp, fs.OpenMode.READ_ONLY | fs.OpenMode.CREATE)) - } - fs.closeSync(kernelFile) - fs.closeSync(vmFile) - fs.closeSync(isolateFile) - Log.d(TAG, "copyResource end") - } else { - Log.d(TAG, "no copyResource") - } - } - - /** - * Ensures that Dart VM initialization is complete. - * This method initializes the Dart VM with the appropriate shell arguments - * based on the build mode (debug, profile, or release). - * @param shellArgs - Optional array of shell arguments, will be created if null - */ - ensureInitializationComplete(shellArgs: Array | null) { - if (this.initialized) { - return; - } - if (shellArgs == null) { - shellArgs = new Array(); - } - shellArgs.push("--icu-symbol-prefix=_binary_icudtl_dat"); - shellArgs.push( - "--icu-native-lib-path=" - + this.flutterApplicationInfo!.nativeLibraryDir - + FILE_SEPARATOR + DEFAULT_LIBRARY - ); - - let kernelPath: string = ""; - if (this.flutterApplicationInfo!.isDebugMode) { - Log.d(TAG, "this.initResult!.dataDirPath=" + this.initResult!.dataDirPath) - const snapshotAssetPath = - this.initResult!.dataDirPath + FILE_SEPARATOR + this.flutterApplicationInfo!.flutterAssetsDir; - kernelPath = snapshotAssetPath + FILE_SEPARATOR + DEFAULT_KERNEL_BLOB; - shellArgs.push("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath); - shellArgs.push("--" + VM_SNAPSHOT_DATA_KEY + "=" + this.flutterApplicationInfo!.vmSnapshotData); - shellArgs.push( - "--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + this.flutterApplicationInfo!.isolateSnapshotData); - shellArgs.push('--enable-checked-mode') - shellArgs.push('--verbose-logging') - } else { - shellArgs.push( - "--" + AOT_SHARED_LIBRARY_NAME + "=" + this.flutterApplicationInfo!.aotSharedLibraryName); - shellArgs.push( - "--" - + AOT_SHARED_LIBRARY_NAME - + "=" - + this.flutterApplicationInfo!.nativeLibraryDir - + FILE_SEPARATOR - + this.flutterApplicationInfo!.aotSharedLibraryName); - - const snapshotAssetPath = - this.initResult!.dataDirPath + FILE_SEPARATOR + this.flutterApplicationInfo!.flutterAssetsDir; - - if (this.flutterApplicationInfo!.isProfile) { - shellArgs.push("--" + AOT_VMSERVICE_SHARED_LIBRARY_NAME + "=" + VMSERVICE_SNAPSHOT_LIBRARY); - } - } - shellArgs.push("--cache-dir-path=" + this.initResult!.engineCachesPath); - if (StringUtils.isNotEmpty(this.flutterApplicationInfo!.domainNetworkPolicy)) { - shellArgs.push("--domain-network-policy=" + this.flutterApplicationInfo!.domainNetworkPolicy); - } - - const resourceCacheMaxBytesThreshold = 1080 * 1920 * 12 * 4; - shellArgs.push("--resource-cache-max-bytes-threshold=" + resourceCacheMaxBytesThreshold); - - shellArgs.push("--prefetched-default-font-manager"); - - shellArgs.push("--leak-vm=" + true); - - if (this.isEnableImpeller == true && deviceInfo.productModel != "emulator") { - shellArgs.push("--enable-impeller"); - Log.d(TAG, "Enable Impeller in Ohos."); - } else { - Log.d(TAG, "Do not find enableImpeller tag or enableImpeller tag set to false, enable Skia in Ohos."); - } - - // Final initialization operation - const costTime = Date.now() - this.initStartTimestampMillis; - this.flutterNapi.init( - this.context!, - shellArgs, - kernelPath, - this.initResult!.appStoragePath, - this.initResult!.engineCachesPath!, - costTime - ); - this.initialized = true; - Log.d(TAG, "ensureInitializationComplete") - } - - /** - * Finds the path to the Flutter app bundle. - * @returns The path to the Flutter assets directory, or empty string if not initialized - */ - findAppBundlePath(): string { - return this.flutterApplicationInfo == null ? "" : this.flutterApplicationInfo!.flutterAssetsDir; - } - - /** - * Gets the lookup key for an asset file. - * @param asset - The asset file path - * @param packageName - Optional package name for package-specific assets - * @returns The full asset path lookup key - */ - getLookupKeyForAsset(asset: string, packageName?: string): string { - if (typeof packageName === 'string' && packageName.trim().length > 0) { - return this.fullAssetPathFrom('packages' + FILE_SEPARATOR + packageName + FILE_SEPARATOR + asset); - } - return this.fullAssetPathFrom(asset); - } - - /** - * Gets the full asset path from a relative file path. - * @param filePath - The relative file path - * @returns The full asset path, or empty string if not initialized - */ - fullAssetPathFrom(filePath: string): string { - return this.flutterApplicationInfo == null ? "" : this.flutterApplicationInfo!.flutterAssetsDir + "/" + filePath; - } - - private checkTimestamp(dataDir: string): string | null { - let bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT); - const expectedTimestamp = TIMESTAMP_PREFIX + bundleInfo.versionCode + "-" + bundleInfo.updateTime; - const existingTimestamps = this.getExistingTimestamps(dataDir); - if (existingTimestamps == null) { - Log.i(TAG, "No extracted resources found"); - return expectedTimestamp; - } - - if (existingTimestamps.length == 1) { - Log.i(TAG, "Found extracted resources " + existingTimestamps[0]); - } - - if (existingTimestamps.length != 1 || !(expectedTimestamp == existingTimestamps[0])) { - Log.i(TAG, "Resource version mismatch " + expectedTimestamp); - return expectedTimestamp; - } - - return null; - } - - private getExistingTimestamps(dataDir: string): string[] { - return fs.accessSync(dataDir) ? fs.listFileSync(dataDir, { - filter: { - displayName: [`${TIMESTAMP_PREFIX}*`] - } - }) : new Array(); - } - - /** - * Checks if the FlutterLoader has been initialized. - * @returns true if initialized, false otherwise - */ - isInitialized(): boolean { - return this.initialized; - } -} - -/** - * Contains initialization result paths for the Flutter engine. - */ -class InitResult { - appStoragePath: string; - engineCachesPath: string; - dataDirPath: string; - - /** - * Constructs a new InitResult instance. - * @param appStoragePath - Path to the application storage directory - * @param engineCachesPath - Path to the engine caches directory - * @param dataDirPath - Path to the data directory - */ - constructor(appStoragePath: string, - engineCachesPath: string, - dataDirPath: string) { - this.appStoragePath = appStoragePath; - this.engineCachesPath = engineCachesPath; - this.dataDirPath = dataDirPath; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorView.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorView.ets deleted file mode 100644 index 8db1a16..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorView.ets +++ /dev/null @@ -1,170 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterMutatorView.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import ArrayList from '@ohos.util.ArrayList'; -import matrix4 from '@ohos.matrix4'; -import { DVModel, DVModelEvents, DVModelParameters } from '../../../view/DynamicView/dynamicView'; -import { createDVModelFromJson } from '../../../view/DynamicView/dynamicViewJson'; -import OhosTouchProcessor from '../../ohos/OhosTouchProcessor'; -import { FlutterMutator, FlutterMutatorsStack } from './FlutterMutatorsStack' -import Any from '../../../plugin/common/Any'; - -/** - * View that applies Flutter mutators (transforms, clips) to a dynamic view model. - * This class manages the layout and mutator application for platform views. - */ -export class FlutterMutatorView { - private mutatorsStack: FlutterMutatorsStack | null = null; - private screenDensity: number = 0; - private left: number = 0; - private top: number = 0; - private prevLeft: number = 0; - private prevTop: number = 0; - private onTouch = (touchEvent: Any) => { - let params = this.model.params as Record; - switch (touchEvent.type) { - case TouchType.Down: - this.prevLeft = this.left; - this.prevTop = this.top; - params.translateX = this.left; - params.translateY = this.top; - break; - case TouchType.Move: - params.translateX = this.prevLeft; - params.translateY = this.prevTop; - this.prevLeft = this.left; - this.prevTop = this.top; - break; - case TouchType.Up: - case TouchType.Cancel: - default: - break; - } - } - private model: DVModel = createDVModelFromJson( - new DVModelParam("Column", [], { backgroundColor: Color.Red }, { onTouch: this.onTouch }) - ); - - /** - * Sets listeners for descendant focus change events. - * @param onFocus - Callback invoked when a descendant gains focus - * @param onBlur - Callback invoked when a descendant loses focus - */ - setOnDescendantFocusChangeListener(onFocus: () => void, onBlur: () => void) { - // this.model.events["onFocus"] = onFocus; - // this.model.events["onBlur"] = onBlur; - let events2 = this.model.events as Record; - events2.onFocus = onFocus; - events2.onBlur = onBlur; - } - - /** - * Sets the layout parameters for this view. - * @param parameters - The layout parameters including margin, width, and height - */ - public setLayoutParams(parameters: DVModelParameters): void { - if (this.model.params == null) { - this.model.params = new DVModelParameters(); - } - let params = this.model.params as Record | matrix4.Matrix4Transit>; - let parametersRecord = - parameters as Record | matrix4.Matrix4Transit>; - params.marginLeft = parametersRecord['marginLeft']; - params.marginTop = parametersRecord['marginTop']; - params.width = parametersRecord['width']; - params.height = parametersRecord['height']; - this.left = parametersRecord.marginLeft as number; - this.top = parametersRecord.marginTop as number; - } - - /** - * Adds a child dynamic view model to this view. - * @param model - The DVModel to add as a child - */ - public addDvModel(model: DVModel): void { - this.model?.children.push(model); - } - - /** - * Prepares this view for display by applying mutators and setting layout parameters. - * @param mutatorsStack - The stack of mutators to apply - * @param left - The left position of the view - * @param top - The top position of the view - * @param width - The width of the view - * @param height - The height of the view - */ - public readyToDisplay(mutatorsStack: FlutterMutatorsStack, left: number, top: number, width: number, height: number) { - this.mutatorsStack = mutatorsStack; - this.left = left; - this.top = top; - let parameters = - new DVModelParameters() as Record | matrix4.Matrix4Transit>; - parameters['marginLeft'] = left; - parameters['marginTop'] = top; - parameters['width'] = width; - parameters['height'] = height; - this.setLayoutParams(parameters); - this.dealMutators(); - } - - private dealMutators() { - if (this.mutatorsStack == null) { - return; - } - let paths = this.mutatorsStack.getFinalClippingPaths(); - let rects = this.mutatorsStack.getFinalClippingRects(); - let matrix = this.mutatorsStack.getFinalMatrix(); - let params = this.model.params as Record | matrix4.Matrix4Transit>; - if (!paths.isEmpty()) { - let path = paths.getLast(); - params.pathWidth = path.width; - params.pathHeight = path.height; - params.pathCommands = path.commands; - } - if (!rects.isEmpty()) { - let rect = rects.getLast(); - params.rectWidth = rect.width; - params.rectHeight = rect.height; - params.rectRadius = rect.radius; - } - params.matrix = matrix; - } - - /** - * Gets the dynamic view model for this view. - * @returns The DVModel instance, or undefined if not set - */ - public getDvModel(): DVModel | undefined { - return this.model; - } -} - -/** - * Parameters for creating a dynamic view model. - */ -class DVModelParam { - compType: string - children: [] - attributes: Any - events: Any - - /** - * Constructs a new DVModelParam instance. - * @param compType - The component type - * @param children - Array of child components - * @param attributes - Component attributes - * @param events - Component event handlers - */ - constructor(compType: string, children: [], attributes: Any, events: Any) { - this.compType = compType; - this.children = children; - this.attributes = attributes; - this.events = events; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorsStack.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorsStack.ets deleted file mode 100644 index 09f71ec..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/mutatorsstack/FlutterMutatorsStack.ets +++ /dev/null @@ -1,210 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterMutatorsStack.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import matrix4 from '@ohos.matrix4' -import List from '@ohos.util.List'; - -/** - * Types of mutators that can be applied to Flutter views. - */ -export enum FlutterMutatorType { - CLIP_RECT, - CLIP_PATH, - TRANSFORM, - OPACITY -} - -/** - * Represents a rectangular clipping region. - */ -class Rect { - width: number; - height: number; - radius: string | number | Array; - - /** - * Constructs a new Rect instance. - * @param width - The width of the rectangle - * @param height - The height of the rectangle - * @param radius - Optional corner radius for rounded rectangles - */ - constructor(width: number, height: number, radius?: string | number | Array) { - this.width = width; - this.height = height; - this.radius = radius ?? 0; - } -} - -/** - * Represents a path-based clipping region. - */ -class Path { - width: number | string; - height: number | string; - commands: string; - - /** - * Constructs a new Path instance. - * @param width - The width of the path - * @param height - The height of the path - * @param commands - Optional SVG path commands - */ - constructor(width: number | string, height: number | string, commands?: string) { - this.width = width; - this.height = height; - this.commands = commands ?? ''; - } -} - -/** - * Represents a single mutator operation (transform, clip rect, or clip path). - */ -export class FlutterMutator { - private matrix: matrix4.Matrix4Transit | null = null; - private rect: Rect = new Rect(0, 0); - private path: Path = new Path(0, 0); - - /** - * Constructs a new FlutterMutator instance. - * @param args - Either a transformation matrix, a clipping rectangle, or a clipping path - */ - constructor(args: matrix4.Matrix4Transit | Rect | Path) { - if (args instanceof Rect) { - this.rect = args; - } else if (args instanceof Path) { - this.path = args; - } else { - this.matrix = args; - } - } - - /** - * Gets the transformation matrix, if this mutator is a transform. - * @returns The transformation matrix, or null if not a transform - */ - public getMatrix(): matrix4.Matrix4Transit | null { - return this.matrix; - } - - /** - * Gets the clipping rectangle, if this mutator is a clip rect. - * @returns The clipping rectangle - */ - public getRect() { - return this.rect; - } - - /** - * Gets the clipping path, if this mutator is a clip path. - * @returns The clipping path - */ - public getPath() { - return this.path; - } -} - -/** - * Stack of mutators that can be applied to Flutter views. - * This class manages transformations, clipping rectangles, and clipping paths - * that are applied in sequence to create the final view appearance. - */ -export class FlutterMutatorsStack { - private mutators: List; - private finalClippingPaths: List; - private finalClippingRects: List; - private finalMatrix: matrix4.Matrix4Transit; - - /** - * Constructs a new FlutterMutatorsStack instance. - */ - constructor() { - this.mutators = new List(); - this.finalClippingPaths = new List(); - this.finalClippingRects = new List(); - this.finalMatrix = matrix4.identity(); - } - - /** - * Pushes a transformation matrix onto the mutators stack. - * @param values - Array of 16 numbers representing a 4x4 transformation matrix - */ - public pushTransform(values: Array): void { - if (values.length != 16) { - return; - } - let index = 0; - let matrix = matrix4.init( - [values[index++], values[index++], values[index++], values[index++], - values[index++], values[index++], values[index++], values[index++], - values[index++], values[index++], values[index++], values[index++], - values[index++], values[index++], values[index++], values[index++]]); - let mutator = new FlutterMutator(matrix); - this.mutators.add(mutator); - this.finalMatrix.combine(matrix); - } - - /** - * Pushes a rectangular clipping region onto the mutators stack. - * @param width - The width of the clipping rectangle - * @param height - The height of the clipping rectangle - * @param radius - Optional corner radius for rounded rectangles - */ - public pushClipRect(width: number, height: number, radius?: number) { - let rect = new Rect(width, height, radius); - let mutator = new FlutterMutator(rect); - this.mutators.add(mutator); - this.finalClippingRects.add(rect); - } - - /** - * Pushes a path-based clipping region onto the mutators stack. - * @param width - The width of the clipping path - * @param height - The height of the clipping path - * @param command - Optional SVG path commands - */ - public pushClipPath(width: number, height: number, command?: string) { - let path = new Path(width, height, command); - let mutator = new FlutterMutator(path); - this.mutators.add(mutator); - this.finalClippingPaths.add(path); - } - - /** - * Gets all mutators in the stack. - * @returns List of all mutators - */ - public getMutators() { - return this.mutators; - } - - /** - * Gets all final clipping paths. - * @returns List of all clipping paths - */ - public getFinalClippingPaths() { - return this.finalClippingPaths; - } - - /** - * Gets all final clipping rectangles. - * @returns List of all clipping rectangles - */ - public getFinalClippingRects() { - return this.finalClippingRects; - } - - /** - * Gets the final combined transformation matrix. - * @returns The combined transformation matrix from all transforms - */ - public getFinalMatrix() { - return this.finalMatrix; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin.ets deleted file mode 100644 index 990ded7..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin.ets +++ /dev/null @@ -1,225 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterPlugin.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import common from '@ohos.app.ability.common'; -import { BinaryMessenger } from '../../../plugin/common/BinaryMessenger'; -import PlatformViewFactory from '../../../plugin/platform/PlatformViewFactory'; -import PlatformViewRegistry from '../../../plugin/platform/PlatformViewRegistry'; -import { TextureRegistry } from '../../../view/TextureRegistry'; -import FlutterEngine from '../FlutterEngine'; - -/** - * Interface to be implemented by all Flutter plugins. - * - * A Flutter plugin allows Flutter developers to interact with a host platform, e.g., OpenHarmony, - * via Dart code. It includes platform code, as well as Dart code. A plugin author is responsible - * for setting up an appropriate MethodChannel to communicate between platform code and Dart code. - * - * A Flutter plugin has a lifecycle. First, a developer must add a FlutterPlugin to an instance - * of FlutterEngine. To do this, obtain a PluginRegistry with FlutterEngine.getPlugins(), then call - * PluginRegistry.add(FlutterPlugin), passing the instance of the Flutter plugin. During the call - * to PluginRegistry.add(FlutterPlugin), the FlutterEngine will invoke onAttachedToEngine(FlutterPluginBinding) - * on the given FlutterPlugin. If the FlutterPlugin is removed from the FlutterEngine via - * PluginRegistry.remove(pluginClassName), or if the FlutterEngine is destroyed, the FlutterEngine will invoke - * onDetachedFromEngine(FlutterPluginBinding) on the given FlutterPlugin. - * - * Once a FlutterPlugin is attached to a FlutterEngine, the plugin's code is permitted to access - * and invoke methods on resources within the FlutterPlugin.FlutterPluginBinding that the FlutterEngine - * gave to the FlutterPlugin in onAttachedToEngine(FlutterPluginBinding). This includes, for example, - * the application Context for the running app. - * - * The FlutterPlugin.FlutterPluginBinding provided in onAttachedToEngine(FlutterPluginBinding) is - * no longer valid after the execution of onDetachedFromEngine(FlutterPluginBinding). Do not access - * any properties of the FlutterPlugin.FlutterPluginBinding after the completion of - * onDetachedFromEngine(FlutterPluginBinding). - * - * To register a MethodChannel, obtain a BinaryMessenger via the FlutterPlugin.FlutterPluginBinding. - * - * An OpenHarmony Flutter plugin may require access to app resources or other artifacts that can only - * be retrieved through a Context. Developers can access the application context via - * FlutterPlugin.FlutterPluginBinding.getApplicationContext(). - * - * Some plugins may require access to the UIAbility that is displaying a Flutter experience, or - * may need to react to UIAbility lifecycle events, e.g., onCreate(), onWindowStageCreate(), onForeground(), - * onBackground(), onWindowStageDestroy(), onDestroy(). Any such plugin should implement AbilityAware - * in addition to implementing FlutterPlugin. AbilityAware provides callback hooks that expose access - * to an associated UIAbility and its Lifecycle. All plugins must respect the possibility that a Flutter - * experience may never be associated with a UIAbility, e.g., when Flutter is used for background - * behavior. Additionally, all plugins must respect that UIAbilities may come and go over time, thus - * requiring plugins to cleanup resources and recreate those resources as the UIAbility comes and goes. - */ -export interface FlutterPlugin { - /** - * Gets the unique class name of this plugin. - * Similar to Android's Class, but in TypeScript this must be user-defined. - * @returns The unique class name of this plugin - */ - getUniqueClassName(): string - - /** - * This FlutterPlugin has been associated with a FlutterEngine instance. - * - * Relevant resources that this FlutterPlugin may need are provided via the binding. - * The binding may be cached and referenced until onDetachedFromEngine is invoked and returns. - * @param binding - The FlutterPluginBinding providing access to engine resources - */ - onAttachedToEngine(binding: FlutterPluginBinding): void; - - /** - * This FlutterPlugin has been removed from a FlutterEngine instance. - * - * The binding passed to this method is the same instance that was passed in - * onAttachedToEngine. It is provided again in this method as a convenience. - * The binding may be referenced during the execution of this method, but it - * must not be cached or referenced after this method returns. - * - * FlutterPlugins should release all resources in this method. - * @param binding - The FlutterPluginBinding that was provided in onAttachedToEngine - */ - onDetachedFromEngine(binding: FlutterPluginBinding): void; -} - -/** - * Binding that provides Flutter plugins with access to Flutter engine resources. - * This class holds references to the engine, messenger, assets, and registries that plugins may need. - */ -export class FlutterPluginBinding { - private applicationContext: common.Context; - private flutterEngine: FlutterEngine; - private binaryMessenger: BinaryMessenger; - private flutterAssets: FlutterAssets; - private textureRegistry: TextureRegistry; - private platformViewRegistry: PlatformViewRegistry; - - /** - * Constructs a new FlutterPluginBinding instance. - * @param applicationContext - The application context - * @param flutterEngine - The FlutterEngine instance - * @param binaryMessenger - The BinaryMessenger for platform communication - * @param flutterAssets - The FlutterAssets for accessing Flutter assets - * @param textureRegistry - The TextureRegistry for managing textures - * @param platformViewRegistry - Optional PlatformViewRegistry, will be created if not provided - */ - constructor(applicationContext: common.Context, flutterEngine: FlutterEngine, binaryMessenger: BinaryMessenger, - flutterAssets: FlutterAssets, textureRegistry: TextureRegistry, platformViewRegistry?: PlatformViewRegistry) { - this.applicationContext = applicationContext; - this.flutterEngine = flutterEngine; - this.binaryMessenger = binaryMessenger; - this.flutterAssets = flutterAssets; - this.textureRegistry = textureRegistry; - this.platformViewRegistry = platformViewRegistry ?? new EmptyPlatformViewRegistry(); - } - - /** - * Gets the application context. - * @returns The application context - */ - getApplicationContext(): common.Context { - return this.applicationContext; - } - - /** - * Gets the FlutterEngine instance. - * @returns The FlutterEngine instance - */ - getFlutterEngine(): FlutterEngine { - return this.flutterEngine; - } - - /** - * Gets the BinaryMessenger for platform communication. - * @returns The BinaryMessenger instance - */ - getBinaryMessenger(): BinaryMessenger { - return this.binaryMessenger; - } - - /** - * Gets the FlutterAssets for accessing Flutter assets. - * @returns The FlutterAssets instance - */ - getFlutterAssets(): FlutterAssets { - return this.flutterAssets; - } - - /** - * Gets the TextureRegistry for managing textures. - * @returns The TextureRegistry instance - */ - getTextureRegistry(): TextureRegistry { - return this.textureRegistry; - } - - /** - * Gets the PlatformViewRegistry for managing platform views. - * @returns The PlatformViewRegistry instance - */ - public getPlatformViewRegistry(): PlatformViewRegistry { - return this.platformViewRegistry; - } -} - -/** Provides Flutter plugins with access to Flutter asset information. */ -export interface FlutterAssets { - /** - * Returns the relative file path to the Flutter asset with the given name, including the file's - * extension, e.g., "myImage.jpg". - * - * The returned file path is relative to the OpenHarmony app's standard assets directory. - * Therefore, the returned path is appropriate to pass to OpenHarmony's ResourceManager, but - * the path is not appropriate to load as an absolute path. - * @param assetFileName - The name of the asset file - * @returns The relative file path to the asset - */ - getAssetFilePathByName(assetFileName: string): string; - - /** - * Same as getAssetFilePathByName but with added support for an explicit bundleName. - * @param assetFileName - The name of the asset file - * @param bundleName - The bundle name - * @returns The relative file path to the asset - */ - getAssetFilePathByName(assetFileName: string, bundleName: string): string; - - /** - * Returns the relative file path to the Flutter asset with the given subpath, including the - * file's extension, e.g., "/dir1/dir2/myImage.jpg". - * - * The returned file path is relative to the OpenHarmony app's standard assets directory. - * Therefore, the returned path is appropriate to pass to OpenHarmony's ResourceManager, but - * the path is not appropriate to load as an absolute path. - * @param assetSubpath - The subpath of the asset - * @returns The relative file path to the asset - */ - getAssetFilePathBySubpath(assetSubpath: string): string; - - /** - * Same as getAssetFilePathBySubpath but with added support for an explicit bundleName. - * @param assetSubpath - The subpath of the asset - * @param bundleName - The bundle name - * @returns The relative file path to the asset - */ - getAssetFilePathBySubpath(assetSubpath: string, bundleName: string): string; -} - -/** - * Empty implementation of PlatformViewRegistry that does nothing. - */ -class EmptyPlatformViewRegistry implements PlatformViewRegistry { - /** - * Attempts to register a view factory, but always returns false. - * @param viewTypeId - The view type identifier - * @param factory - The PlatformViewFactory to register - * @returns Always returns false - */ - registerViewFactory(viewTypeId: string, factory: PlatformViewFactory): boolean { - return false; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/PluginRegistry.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/PluginRegistry.ets deleted file mode 100644 index aa81268..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/PluginRegistry.ets +++ /dev/null @@ -1,73 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PluginRegistry.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { FlutterPlugin } from './FlutterPlugin'; - -/** - * Registry for managing Flutter plugins. - * This interface provides methods to add, remove, and query plugins attached to a FlutterEngine. - */ -export default interface PluginRegistry { - /** - * Attaches the given plugin to the FlutterEngine associated with this PluginRegistry. - * @param plugin - The FlutterPlugin to attach - */ - add(plugin: FlutterPlugin): void; - - /** - * Attaches the given plugins to the FlutterEngine associated with this PluginRegistry. - * @param plugins - The set of FlutterPlugins to attach - */ - addList(plugins: Set): void; - - /** - * Checks if a plugin of the given type is currently attached to the FlutterEngine - * associated with this PluginRegistry. - * @param pluginClassName - The class name of the plugin to check - * @returns True if the plugin is attached, false otherwise - */ - has(pluginClassName: string): boolean; - - /** - * Gets the instance of a plugin that is currently attached to the FlutterEngine - * associated with this PluginRegistry, which matches the given pluginClassName. - * - * If no matching plugin is found, null is returned. - * @param pluginClassName - The class name of the plugin to get - * @returns The FlutterPlugin instance, or null if not found - */ - get(pluginClassName: string): FlutterPlugin; - - /** - * Detaches the plugin of the given type from the FlutterEngine - * associated with this PluginRegistry. - * - * If no such plugin exists, this method does nothing. - * @param pluginClassName - The class name of the plugin to remove - */ - remove(pluginClassName: string): void; - - /** - * Detaches the plugins of the given types from the FlutterEngine - * associated with this PluginRegistry. - * - * If no such plugins exist, this method does nothing. - * @param pluginClassNames - The set of plugin class names to remove - */ - removeList(pluginClassNames: Set): void; - - /** - * Detaches all plugins that are currently attached to the FlutterEngine - * associated with this PluginRegistry. - * - * If no plugins are currently attached, this method does nothing. - */ - removeAll(): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ets deleted file mode 100644 index 43151f9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ets +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2013 The Flutter Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. -*/ - -import { AbilityPluginBinding } from './AbilityPluginBinding'; - -/** - * FlutterPlugin that is interested in UIAbility lifecycle events related to a FlutterEngine - * running within the given UIAbility. - */ -export default interface AbilityAware { - /** - * This AbilityAware FlutterPlugin is now associated with a UIAbility. - * - * This method can be invoked in 1 of 2 situations: - * - * This AbilityAware FlutterPlugin was just added to a FlutterEngine that was already - * connected to a running UIAbility. - * This AbilityAware FlutterPlugin was already added to a FlutterEngine and that - * FlutterEngine was just connected to a UIAbility. - * - * The given AbilityPluginBinding contains UIAbility-related references that an AbilityAware - * FlutterPlugin may require, such as a reference to the actual UIAbility in question. - * The AbilityPluginBinding may be referenced until either onDetachedFromAbilityForConfigChanges - * or onDetachedFromAbility is invoked. At the conclusion of either of those methods, the - * binding is no longer valid. Clear any references to the binding or its resources, and do not - * invoke any further methods on the binding or its resources. - * @param binding - The AbilityPluginBinding providing access to UIAbility resources - */ - onAttachedToAbility(binding: AbilityPluginBinding): void; - - /** - * This plugin has been detached from a UIAbility. - * - * Detachment can occur for a number of reasons: - * - * The app is no longer visible and the UIAbility instance has been destroyed. - * The FlutterEngine that this plugin is connected to has been detached from its FlutterView. - * This AbilityAware plugin has been removed from its FlutterEngine. - * - * By the end of this method, the UIAbility that was made available in - * onAttachedToAbility is no longer valid. Any references to the - * associated UIAbility or AbilityPluginBinding should be cleared. - * - * Any Lifecycle listeners that were registered in onAttachedToAbility or - * onReattachedToAbilityForConfigChanges should be deregistered here to - * avoid a possible memory leak and other side effects. - */ - onDetachedFromAbility(): void; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ets deleted file mode 100644 index 61490f8..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ets +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013 The Flutter Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. -*/ - -import AbilityConstant from '@ohos.app.ability.AbilityConstant'; -import Want from '@ohos.app.ability.Want'; -import UIAbility from '@ohos.app.ability.UIAbility'; -import ExclusiveAppComponent from '../../../ohos/ExclusiveAppComponent'; - -/** - * Interface for controlling ability-related operations in Flutter engines. - * This interface provides methods to attach/detach from abilities and handle ability lifecycle events. - */ -export default interface ActivityControlSurface { - /** - * Attaches this surface to an exclusive app component (UIAbility). - * @param exclusiveActivity - The exclusive app component to attach to - */ - attachToAbility(exclusiveActivity: ExclusiveAppComponent): void; - - /** - * Detaches this surface from the current ability. - */ - detachFromAbility(): void; - - /** - * Handles a new Want event from the ability. - * @param want - The Want object containing the intent - * @param launchParams - The launch parameters - */ - onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; - - /** - * Handles window focus change events from the ability. - * @param hasFocus - Whether the window has focus - */ - onWindowFocusChanged(hasFocus: boolean): void; - - /** - * Handles save state requests from the ability. - * @param reason - The reason for saving state - * @param wantParam - Parameters to save - * @returns The result of the save state operation - */ - onSaveState(reason: AbilityConstant.StateType, wantParam: Record): AbilityConstant.OnSaveResult; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ets deleted file mode 100644 index 4fc1e24..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ets +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013 The Flutter Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. -*/ - -import UIAbility from '@ohos.app.ability.UIAbility' -import Want from '@ohos.app.ability.Want'; -import AbilityConstant from '@ohos.app.ability.AbilityConstant'; - -/** - * Binding that provides plugins with access to UIAbility-related resources. - * This interface allows plugins to access the ability and register for ability lifecycle events. - */ -export interface AbilityPluginBinding { - /** - * Gets the UIAbility instance. - * @returns The UIAbility instance - */ - getAbility(): UIAbility; - - /** - * Adds a listener that is invoked whenever the associated UIAbility's onNewWant method is invoked. - * @param listener - The NewWantListener to add - */ - addOnNewWantListener(listener: NewWantListener): void; - - /** - * Removes a listener that was added in addOnNewWantListener. - * @param listener - The NewWantListener to remove - */ - removeOnNewWantListener(listener: NewWantListener): void; - - /** - * Adds a listener that is invoked whenever the associated UIAbility's windowStageEvent method is invoked. - * @param listener - The WindowFocusChangedListener to add - */ - addOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void; - - /** - * Removes a listener that was added in addOnWindowFocusChangedListener. - * @param listener - The WindowFocusChangedListener to remove - */ - removeOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void; - - /** - * Adds a listener that is invoked when the associated UIAbility saves and restores instance state. - * @param listener - The OnSaveStateListener to add - */ - addOnSaveStateListener(listener: OnSaveStateListener): void; - - /** - * Removes a listener that was added in addOnSaveStateListener. - * @param listener - The OnSaveStateListener to remove - */ - removeOnSaveStateListener(listener: OnSaveStateListener): void; -} - -/** - * Delegate interface for handling new wants on behalf of the main UIAbility. - */ -export interface NewWantListener { - /** - * Called when a new want is started for the UIAbility. - * @param want - The new want that was started for the UIAbility - * @param launchParams - The launch parameters - */ - onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; -} - -/** - * Delegate interface for handling window focus changes on behalf of the main UIAbility. - */ -export interface WindowFocusChangedListener { - /** - * Called when the window focus changes. - * @param hasFocus - Whether the window has focus - */ - onWindowFocusChanged(hasFocus: boolean): void; -} - -/** - * Delegate interface for handling save state events. - */ -export interface OnSaveStateListener { - /** - * Invoked when the associated UIAbility saves and restores instance state. - * @param reason - The reason for saving state - * @param wantParam - Parameters to save - * @returns The result of the save state operation - */ - onSaveState(reason: AbilityConstant.StateType, wantParam: Record): AbilityConstant.OnSaveResult; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets deleted file mode 100644 index 1cb48a0..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/renderer/FlutterRenderer.ets +++ /dev/null @@ -1,269 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import image from '@ohos.multimedia.image'; -import { BusinessError } from '@ohos.base'; -import { SurfaceTextureEntry, TextureRegistry } from '../../../view/TextureRegistry'; -import { FlutterAbility } from '../../ohos/FlutterAbility'; -import FlutterNapi from '../FlutterNapi'; -import Log from '../../../util/Log'; - -const TAG = "FlutterRenderer" - -/** - * Renderer for Flutter content that manages textures and rendering operations. - * This class implements TextureRegistry and provides methods to register and manage textures - * for use in Flutter applications. - */ -export class FlutterRenderer implements TextureRegistry { - private flutterNapi: FlutterNapi; - private static globalTextureId: number = 0; - - /** - * Constructs a new FlutterRenderer instance. - * @param flutterNapi - The FlutterNapi instance for native communication - */ - constructor(flutterNapi: FlutterNapi) { - this.flutterNapi = flutterNapi; - } - - /** - * @deprecated since 3.7 - */ - createSurfaceTexture(): SurfaceTextureEntry { - let receiver: image.ImageReceiver = this.getImageReceiver(); - return this.registerSurfaceTexture(receiver); - } - - /** - * Gets the next available texture ID. - * @returns A unique texture ID - */ - getTextureId(): number { - let nextTextureId: number = FlutterRenderer.globalTextureId + 1; - FlutterRenderer.globalTextureId = FlutterRenderer.globalTextureId + 1; - Log.i(TAG, "getTextureId: " + nextTextureId) - return nextTextureId; - } - - /** - * Registers a texture with the Flutter engine. - * @param textureId - The texture ID to register - * @returns A SurfaceTextureEntry containing the registered texture information - */ - registerTexture(textureId: number): SurfaceTextureEntry { - let surfaceTextureRegistryEntry = new SurfaceTextureRegistryEntry(textureId); - let surfaceId = this.flutterNapi.registerTexture(textureId); - Log.i(TAG, "registerTexture, surfaceId=" + surfaceId); - surfaceTextureRegistryEntry.setSurfaceId(surfaceId); - let nativeWindowId = this.flutterNapi.getTextureNativeWindowId(textureId); - surfaceTextureRegistryEntry.setNativeWindowId(nativeWindowId); - let nativeWindowPtr = this.flutterNapi.getTextureNativeWindowPtr(textureId); - surfaceTextureRegistryEntry.setNativeWindowPtr(nativeWindowPtr); - return surfaceTextureRegistryEntry; - } - - /** - * @deprecated since 3.7 - */ - registerSurfaceTexture(receiver: image.ImageReceiver): SurfaceTextureEntry { - let nextTextureId: number = FlutterRenderer.globalTextureId + 1; - FlutterRenderer.globalTextureId = FlutterRenderer.globalTextureId + 1; - let surfaceTextureRegistryEntry = new SurfaceTextureRegistryEntry(nextTextureId); - return surfaceTextureRegistryEntry; - } - - /** - * Registers a PixelMap as a texture. - * @param pixelMap - The PixelMap to register - * @returns The texture ID assigned to the PixelMap - */ - registerPixelMap(pixelMap: PixelMap): number { - let nextTextureId: number = this.getTextureId(); - this.flutterNapi.registerPixelMap(nextTextureId, pixelMap); - return nextTextureId; - } - - /** - * Sets the background PixelMap for a texture. - * @param textureId - The texture ID - * @param pixelMap - The PixelMap to use as background - */ - setTextureBackGroundPixelMap(textureId: number, pixelMap: PixelMap): void { - this.flutterNapi.setTextureBackGroundPixelMap(textureId, pixelMap); - } - - /** - * @deprecated since 3.7 - */ - setTextureBackGroundColor(textureId: number, color: number): void { - this.flutterNapi.setTextureBackGroundColor(textureId, color); - } - - /** - * Sets the buffer size for a texture. - * @param textureId - The texture ID - * @param width - The buffer width - * @param height - The buffer height - */ - setTextureBufferSize(textureId: number, width: number, height: number): void { - this.flutterNapi.setTextureBufferSize(textureId, width, height); - } - - /** - * Notifies the Flutter engine that a texture is being resized. - * @param textureId - The texture ID - * @param width - The new width - * @param height - The new height - */ - notifyTextureResizing(textureId: number, width: number, height: number): void { - this.flutterNapi.notifyTextureResizing(textureId, width, height); - } - - /** - * @deprecated since 3.22 - * @useinstead FlutterRenderer#setExternalNativeImagePtr - */ - setExternalNativeImage(textureId: number, native_image: number): boolean { - return this.flutterNapi.setExternalNativeImage(textureId, native_image); - } - - /** - * Sets an external native image pointer for a texture. - * @param textureId - The texture ID - * @param native_image_ptr - The native image pointer as a bigint - * @returns true if successful, false otherwise - */ - setExternalNativeImagePtr(textureId: number, native_image_ptr: bigint): boolean { - return this.flutterNapi.setExternalNativeImagePtr(textureId, native_image_ptr); - } - - /** - * Resets an external texture. - * @param textureId - The texture ID - * @param need_surfaceId - Whether a surface ID is needed - * @returns The result code - */ - resetExternalTexture(textureId: number, need_surfaceId: boolean): number { - return this.flutterNapi.resetExternalTexture(textureId, need_surfaceId); - } - - /** - * Unregisters a texture from the Flutter engine. - * @param textureId - The texture ID to unregister - */ - unregisterTexture(textureId: number): void { - this.flutterNapi.unregisterTexture(textureId); - } - - /** - * Called when the system needs to trim memory. - * @param level - The memory trim level - */ - onTrimMemory(level: number) { - throw new Error('Method not implemented.'); - } - - /** - * @deprecated since 3.7 - */ - private getImageReceiver(): image.ImageReceiver { - let receiver: image.ImageReceiver = image.createImageReceiver(640, 480, 4, 8); - if (receiver !== undefined) { - Log.i(TAG, '[camera test] ImageReceiver is ok'); - } else { - Log.i(TAG, '[camera test] ImageReceiver is not ok'); - } - receiver?.on('imageArrival', () => { - receiver.readNextImage().then(() => { receiver.release() }) - }) - return receiver; - } - -} - -/** - * Entry in the surface texture registry representing a registered texture. - * This class holds information about a texture including its ID, surface ID, and native window information. - */ -export class SurfaceTextureRegistryEntry implements SurfaceTextureEntry { - private textureId: number = 0; - private surfaceId: number = 0; - private nativeWindowId: number = 0; - private nativeWindowPtr: bigint = BigInt("0"); - private released: boolean = false; - - /** - * Constructs a new SurfaceTextureRegistryEntry instance. - * @param id - The texture ID - */ - constructor(id: number) { - this.textureId = id; - } - - /** - * Gets the texture ID. - * @returns The texture ID - */ - getTextureId(): number { - return this.textureId; - } - - /** - * Gets the surface ID. - * @returns The surface ID - */ - getSurfaceId(): number { - return this.surfaceId; - } - - /** - * @deprecated since 3.22 - * @useinstead SurfaceTextureRegistryEntry#getNativeWindowPtr - */ - getNativeWindowId(): number { - return this.nativeWindowId; - } - - /** - * Gets the native window pointer. - * @returns The native window pointer as a bigint - */ - getNativeWindowPtr(): bigint { - return this.nativeWindowPtr; - } - - /** - * Sets the surface ID. - * @param surfaceId - The surface ID to set - */ - setSurfaceId(surfaceId: number): void { - this.surfaceId = surfaceId; - } - - /** - * @deprecated since 3.22 - * @useinstead SurfaceTextureRegistryEntry#setNativeWindowPtr - */ - setNativeWindowId(nativeWindowId: number): void { - this.nativeWindowId = nativeWindowId; - } - - /** - * Sets the native window pointer. - * @param nativeWindowPtr - The native window pointer as a bigint - */ - setNativeWindowPtr(nativeWindowPtr: bigint): void { - this.nativeWindowPtr = nativeWindowPtr; - } - - /** - * Releases this texture entry and frees associated resources. - */ - release() { - throw new Error('Method not implemented.'); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/renderer/FlutterUiDisplayListener.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/renderer/FlutterUiDisplayListener.ets deleted file mode 100644 index 733215c..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/renderer/FlutterUiDisplayListener.ets +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterUiDisplayListener.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * Listener interface for Flutter UI display state changes. - * Implementations of this interface are notified when Flutter UI is displayed or hidden. - */ -export interface FlutterUiDisplayListener { - /** - * Called when Flutter UI is displayed for the first time. - */ - onFlutterUiDisplayed(): void; - - /** - * Called when Flutter UI is no longer displayed. - */ - onFlutterUiNoLongerDisplayed(): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets deleted file mode 100644 index d141f8f..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ets +++ /dev/null @@ -1,259 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on AccessibilityChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; -import BasicMessageChannel, { MessageHandler, Reply } from '../../../plugin/common/BasicMessageChannel'; -import HashMap from '@ohos.util.HashMap'; -import FlutterNapi, { AccessibilityDelegate } from '../FlutterNapi'; -import StandardMessageCodec from '../../../plugin/common/StandardMessageCodec'; -import StringUtils from '../../../util/StringUtils'; -import Any from '../../../plugin/common/Any'; -import flutter from 'libflutter.so'; -import { ByteBuffer } from '../../../util/ByteBuffer'; - -/** - * Channel for handling accessibility-related communication between Flutter and OpenHarmony. - * This channel manages accessibility features, semantics, and accessibility events. - */ -export default class AccessibilityChannel implements MessageHandler { - private static TAG = "AccessibilityChannel"; - private static CHANNEL_NAME = "flutter/accessibility"; - private channel: BasicMessageChannel; - private flutterNapi: FlutterNapi; - private handler: AccessibilityMessageHandler; - private nextReplyId: number = 1; - - /** - * Handles messages from Dart. - * @param message - The message object from Dart - * @param reply - The reply callback to send a response - */ - onMessage(message: object, reply: Reply): void { - if (this.handler == null) { - Log.i(AccessibilityChannel.TAG, "handler == NULL"); - reply.reply(StringUtils.stringToArrayBuffer("")); - return; - } - let annotatedEvent: HashMap = message as HashMap; - let type: string = annotatedEvent.get("type") as string; - let data: HashMap = annotatedEvent.get("data") as HashMap; - - Log.i(AccessibilityChannel.TAG, "Received " + type + " message."); - switch (type) { - case "announce": { - Log.i(AccessibilityChannel.TAG, "Announce"); - let announceMessage: string = data.get("message"); - if (announceMessage != null) { - Log.i(AccessibilityChannel.TAG, "message is " + announceMessage); - this.handler.announce(announceMessage); - } - break; - } - case "tap": { - Log.i(AccessibilityChannel.TAG, "Tag"); - let nodeId: number = annotatedEvent.get("nodeId"); - if (nodeId != null) { - this.handler.onTap(nodeId); - } - break; - } - case "longPress": { - Log.i(AccessibilityChannel.TAG, "LongPress"); - let nodeId: number = annotatedEvent.get("nodeId"); - if (nodeId != null) { - this.handler.onLongPress(nodeId); - } - break; - } - case "tooltip": { - Log.i(AccessibilityChannel.TAG, "ToolTip"); - let tooltipMessage: string = data.get("message"); - if (tooltipMessage != null) { - this.handler.onTooltip(tooltipMessage); - } - break; - } - } - reply.reply(StringUtils.stringToArrayBuffer("")); - } - - /** - * Constructs a new AccessibilityChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - * @param flutterNapi - The FlutterNapi instance for native communication - */ - constructor(dartExecutor: DartExecutor, flutterNapi: FlutterNapi) { - Log.i(AccessibilityChannel.TAG, "Channel entered"); - this.channel = - new BasicMessageChannel(dartExecutor, AccessibilityChannel.CHANNEL_NAME, StandardMessageCodec.INSTANCE); - this.channel.setMessageHandler(this); - this.flutterNapi = flutterNapi; - this.handler = new DefaultHandler(this.flutterNapi); - } - - /** - * Called when OpenHarmony accessibility is enabled. - */ - onOhosAccessibilityEnabled(): void { - let replyId: number = this.nextReplyId++; - this.flutterNapi.setSemanticsEnabledWithRespId(true, replyId); - Log.i(AccessibilityChannel.TAG, "onOhosAccessibilityEnabled = true"); - } - - /** - * Called when OpenHarmony accessibility features change. - * @param accessibilityFeatureFlags - The accessibility feature flags - */ - onOhosAccessibilityFeatures(accessibilityFeatureFlags: number): void { - let replyId: number = this.nextReplyId++; - this.flutterNapi.setAccessibilityFeatures(accessibilityFeatureFlags, replyId); - Log.i(AccessibilityChannel.TAG, "onOhosAccessibilityFeatures"); - } - - /** - * Dispatches a semantics action to Flutter. - * @param virtualViewId - The virtual view ID - * @param action - The accessibility action to dispatch - */ - dispatchSemanticsAction(virtualViewId: number, action: Action): void { - let replyId: number = this.nextReplyId++; - this.flutterNapi.dispatchSemanticsAction(virtualViewId, action, replyId); - Log.i(AccessibilityChannel.TAG, "dispatchSemanticsAction"); - } - - /** - * Sets the accessibility message handler. - * @param handler - The AccessibilityMessageHandler instance - */ - setAccessibilityMessageHandler(handler: AccessibilityMessageHandler): void { - this.handler = handler; - let replyId: number = this.nextReplyId++; - this.flutterNapi.setAccessibilityDelegate(handler, replyId); - } -} - -/** - * Interface for handling accessibility messages. - */ -export interface AccessibilityMessageHandler extends AccessibilityDelegate { - /** - * Announces a message to the user. - * @param message - The message to announce - */ - announce(message: string): void; - - /** - * Handles a tap event on an accessibility node. - * @param nodeId - The ID of the accessibility node - */ - onTap(nodeId: number): void; - - /** - * Handles a long press event on an accessibility node. - * @param nodeId - The ID of the accessibility node - */ - onLongPress(nodeId: number): void; - - /** - * Handles a tooltip event. - * @param nodeId - The tooltip node ID - */ - onTooltip(nodeId: string): void; -} - -/** - * Default implementation of AccessibilityMessageHandler. - * Handles accessibility events and forwards them to the native Flutter engine. - */ -export class DefaultHandler implements AccessibilityMessageHandler { - private static TAG = "AccessibilityMessageHandler"; - private flutterNapi: FlutterNapi; - - /** - * Constructs a new DefaultHandler instance. - * @param flutterNapi - The FlutterNapi instance for native communication - */ - constructor(flutterNapi: FlutterNapi) { - this.flutterNapi = flutterNapi; - } - - /** - * Announces a message to the user. - * @param message - The message to announce - */ - announce(message: string): void { - Log.i(DefaultHandler.TAG, "handler announce."); - flutter.nativeAccessibilityAnnounce(this.flutterNapi.nativeShellHolderId!, message); - } - - /** - * Handles a tap event on an accessibility node. - * @param nodeId - The ID of the accessibility node - */ - onTap(nodeId: number): void { - Log.i(DefaultHandler.TAG, "handler onTap."); - flutter.nativeAccessibilityOnTap(this.flutterNapi.nativeShellHolderId!, nodeId); - } - - /** - * Handles a long press event on an accessibility node. - * @param nodeId - The ID of the accessibility node - */ - onLongPress(nodeId: number): void { - Log.i(DefaultHandler.TAG, "handler onLongPress."); - flutter.nativeAccessibilityOnLongPress(this.flutterNapi.nativeShellHolderId!, nodeId); - } - - /** - * Handles a tooltip event. - * @param message - The tooltip message - */ - onTooltip(message: string): void { - Log.i(DefaultHandler.TAG, "handler onTooltip."); - flutter.nativeAccessibilityOnTooltip(this.flutterNapi.nativeShellHolderId!, message); - } - - /** - * Called when accessibility state changes. - * @param state - The new accessibility state - */ - accessibilityStateChange(state: Boolean): void { - Log.i(DefaultHandler.TAG, "handler accessibilityStateChange"); - } -} - -/** - * Accessibility actions that can be performed on semantic nodes. - */ -export enum Action { - TAP = 1 << 0, - LONG_PRESS = 1 << 1, - SCROLL_LEFT = 1 << 2, - SCROLL_RIGHT = 1 << 3, - SCROLL_UP = 1 << 4, - SCROLL_DOWN = 1 << 5, - INCREASE = 1 << 6, - DECREASE = 1 << 7, - SHOW_ON_SCREEN = 1 << 8, - MOVE_CURSOR_FORWARD_BY_CHARACTER = 1 << 9, - MOVE_CURSOR_BACKWARD_BY_CHARACTER = 1 << 10, - SET_SELECTION = 1 << 11, - COPY = 1 << 12, - CUT = 1 << 13, - PASTE = 1 << 14, - DID_GAIN_ACCESSIBILITY_FOCUS = 1 << 15, - DID_LOSE_ACCESSIBILITY_FOCUS = 1 << 16, - CUSTOM_ACTION = 1 << 17, - DISMISS = 1 << 18, - MOVE_CURSOR_FORWARD_BY_WORD = 1 << 19, - MOVE_CURSOR_BACKWARD_BY_WORD = 1 << 20, - SET_NEXT = 1 << 21, -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/DisplayMetricsChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/DisplayMetricsChannel.ets deleted file mode 100644 index fd44a8a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/DisplayMetricsChannel.ets +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2026 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; -import { common } from '@kit.AbilityKit'; - -const TAG: string = 'DisplayMetricsChannel'; - -export default class DisplayMetricsChannel implements MethodCallHandler { - public channel: MethodChannel; - public context: common.Context; - - onMethodCall(call: MethodCall, result: MethodResult): void { - let method: string = call.method; - Log.i(TAG, "Received '" + method + "' message."); - try { - // More methods are expected to be added here, hence the switch. - switch (method) { - case "updateDpiScale": - let dpiScaleFactor: number = call.argument('dpiScale'); - Log.i(TAG, "Received dpiScaleFactor '" + dpiScaleFactor + "' message."); - this.context.eventHub.emit('changeDevicePixelRatio', dpiScaleFactor) - result.success(true); - break; - default: - result.notImplemented(); - break; - } - } catch (error) { - result.error("error", "UnHandled error: " + JSON.stringify(error), null) - } - } - - constructor(dartExecutor: DartExecutor, context: common.Context) { - this.channel = new MethodChannel(dartExecutor, "flutter/displaymetrics", StandardMethodCodec.INSTANCE); - this.channel.setMethodCallHandler(this); - this.context = context; - } -} - diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ets deleted file mode 100644 index de5ff4f..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ets +++ /dev/null @@ -1,259 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on KeyEventChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; -import { BinaryMessenger } from '../../../plugin/common/BinaryMessenger'; -import Log from '../../../util/Log'; -import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; -import { KeyCode } from '@kit.InputKit'; -import { ModifierKeyMetaInfo } from '../../ohos/KeyboardMap' -import Any from '../../../plugin/common/Any'; - -/** - * Channel for handling keyboard key events between OpenHarmony and Flutter. - * This channel manages communication of key events, including both hardware keyboard events - * and simulated key events for soft-keyboard text-editing. It uses a BasicMessageChannel - * with JSON encoding to send key event data to the Flutter framework. - */ -export default class KeyEventChannel { - private static TAG = "KeyEventChannel"; - private static CHANNEL_NAME = "flutter/keyevent"; - private channel: BasicMessageChannel; - - /** - * Constructs a new KeyEventChannel instance. - * @param binaryMessenger - The BinaryMessenger for sending messages to Dart - */ - constructor(binaryMessenger: BinaryMessenger) { - this.channel = new BasicMessageChannel(binaryMessenger, KeyEventChannel.CHANNEL_NAME, - JSONMessageCodec.INSTANCE); - } - - /** - * Sends a hardware key event to the Flutter framework. - * The event is encoded and sent asynchronously. The response handler will be called - * when Flutter responds, indicating whether the event was handled. - * @param keyEvent - The FlutterKeyEvent containing the OpenHarmony key event data - * @param isKeyUp - Whether this is a key up event (true) or key down event (false) - * @param responseHandler - Handler to receive the response from Flutter indicating if the event was handled - */ - sendFlutterKeyEvent(keyEvent: FlutterKeyEvent, - isKeyUp: boolean, - responseHandler: EventResponseHandler): void { - this.channel.send(this.encodeKeyEvent(keyEvent, isKeyUp), - (message: Object) => { - let isEventHandled = false; - try { - if (message != null) { - const tmp: Record = message as Record; - isEventHandled = tmp["handled"] || false; - } - } catch (e) { - Log.e(KeyEventChannel.TAG, "Unable to unpack JSON message: " + e); - } - responseHandler.onFrameworkResponse(isEventHandled); - } - ); - } - - private encodeKeyEvent(keyEvent: FlutterKeyEvent, isKeyUp: boolean): Map { - let message: Map = new Map(); - message.set("type", isKeyUp ? "keyup" : "keydown"); - message.set("keymap", "ohos"); - message.set("keyCode", keyEvent.event.keyCode); - message.set("deviceId", keyEvent.event.deviceId); - message.set("flags", keyEvent.event.keyText); - // the keyEvent of ohos do not support the getMetaState feature, - // so the flutter-ohos side adapts the getMetaState() method - message.set("metaState", keyEvent.getMetaState()); - message.set("source", keyEvent.event.keySource); - message.set("intentionCode", keyEvent.event.intentionCode); - return message; - } - - /** - * Sends a simulated key event for soft-keyboard text-editing in the input method. - * This method is used for virtual keyboard events (e.g., arrow keys, selection keys) - * that are generated programmatically rather than from hardware input. - * @param keyEvent - The SimulateKeyEvent containing the simulated key data - * @param isKeyUp - Whether this is a key up event (true) or key down event (false) - * @param responseHandler - Handler to receive the response from Flutter indicating if the event was handled - */ - simulateSendFlutterKeyEvent(keyEvent: SimulateKeyEvent, - isKeyUp: boolean, - responseHandler: EventResponseHandler): void { - this.channel.send(this.encodeSimulatedKeyEvent(keyEvent, isKeyUp), - (message: Object) => { - let isEventHandled = false; - try { - if (message !== null) { - const tmp: Record = message as Record; - isEventHandled = tmp["handled"] || false; - } - } catch (e) { - Log.e(KeyEventChannel.TAG, "Unable to unpack JSON message: " + e); - } - responseHandler.onFrameworkResponse(isEventHandled); - } - ); - } - - private encodeSimulatedKeyEvent(keyEvent: SimulateKeyEvent, isKeyUp: boolean): Map { - let message: Map = new Map(); - message.set("type", isKeyUp ? "keyup" : "keydown"); - message.set("keymap", "ohos"); - message.set("keyCode", keyEvent.keyCode); - message.set("flags", keyEvent.keyText); - return message; - } -} - -/** - * Interface for handling event responses from the Flutter framework. - * Implementations of this interface receive callbacks when Flutter processes - * key events and indicates whether the event was handled. - */ -export interface EventResponseHandler { - /** - * Called when Flutter responds to a key event. - * This callback is invoked asynchronously after Flutter processes the key event. - * @param isEventHandled - Whether Flutter handled the event (true) or not (false) - */ - onFrameworkResponse: (isEventHandled: boolean) => void; -} - -/** - * Wrapper for OpenHarmony KeyEvent that provides Flutter-compatible meta state information. - * OpenHarmony KeyEvent does not natively support meta state tracking for modifier keys - * (Ctrl, Alt, Shift) in the same way Flutter expects. This class supplements the missing - * functionality by tracking modifier key states and providing a getMetaState() method - * that returns a bitmask compatible with Flutter's key event handling. - */ -export class FlutterKeyEvent { - /** The underlying OpenHarmony KeyEvent instance. */ - event: KeyEvent; - private mMetaState: number; - private isCtrlPressed: boolean | undefined = false; - private isAltPressed: boolean | undefined = false; - private isShiftPressed: boolean | undefined = false; - - /** - * Constructs a new FlutterKeyEvent instance. - * @param ohosKeyEvent - The OpenHarmony KeyEvent to wrap - */ - constructor(ohosKeyEvent: KeyEvent) { - this.event = ohosKeyEvent; - this.mMetaState = ModifierKeyMetaInfo.NONE; - this.isCtrlPressed = ohosKeyEvent.getModifierKeyState && ohosKeyEvent.getModifierKeyState(['Ctrl']); - this.isAltPressed = ohosKeyEvent.getModifierKeyState && ohosKeyEvent.getModifierKeyState(['Alt']); - this.isShiftPressed = ohosKeyEvent.getModifierKeyState && ohosKeyEvent.getModifierKeyState(['Shift']); - this.didUpdateOhosMetaState(); - } - - /** - * Gets the meta state value as a bitmask. - * This supplements the missing metaState feature of OpenHarmony when pressing - * modifier keys (Ctrl, Alt, Shift) to perform combination key operations. - * The meta state is a bitmask that indicates which modifier keys are currently pressed. - * Currently only supports metaState tracking for Ctrl, Alt, and Shift keys. - * @returns The meta state value as a bitmask indicating pressed modifier keys - */ - getMetaState(): number { - return this.mMetaState; - } - - private didUpdateOhosMetaState(): void { - // The ohos platform only supports whether the Ctrl/Alt/Shift key or combination of them is pressed or not, - // and cannot distinguish the left or right directions. - // Here, the left direction key of ctrl/alt/shift is used by default - if (this.isCtrlPressed != undefined) { - this.updateMetaStateIfNeeded(KeyCode.KEYCODE_CTRL_LEFT, this.isCtrlPressed); - } - if (this.isAltPressed != undefined) { - this.updateMetaStateIfNeeded(KeyCode.KEYCODE_ALT_LEFT, this.isAltPressed); - } - if (this.isShiftPressed != undefined) { - this.updateMetaStateIfNeeded(KeyCode.KEYCODE_SHIFT_LEFT, this.isShiftPressed); - } - } - - private updateMetaStateIfNeeded(keyCode: number, isPressed: boolean): void { - const oldMetaState: number = this.mMetaState; - const newMetaState: number = this.updateMetaState(keyCode, isPressed, oldMetaState); - const isMetaStateChanged: number = oldMetaState ^ newMetaState; - if (isMetaStateChanged) { - this.mMetaState = newMetaState; - } - } - - private updateMetaState(keyCode: number, isPressed: boolean, oldMetaState: number): number { - switch (keyCode) { - case KeyCode.KEYCODE_ALT_LEFT: - return this.setMetaState(ModifierKeyMetaInfo.ALT_LEFT, isPressed, oldMetaState); - case KeyCode.KEYCODE_ALT_RIGHT: - return this.setMetaState(ModifierKeyMetaInfo.ALT_RIGHT, isPressed, oldMetaState); - case KeyCode.KEYCODE_SHIFT_LEFT: - return this.setMetaState(ModifierKeyMetaInfo.SHIFT_LEFT, isPressed, oldMetaState); - case KeyCode.KEYCODE_SHIFT_RIGHT: - return this.setMetaState(ModifierKeyMetaInfo.SHIFT_RIGHT, isPressed, oldMetaState); - case KeyCode.KEYCODE_CTRL_LEFT: - return this.setMetaState(ModifierKeyMetaInfo.CTRL_LEFT, isPressed, oldMetaState); - case KeyCode.KEYCODE_CTRL_RIGHT: - return this.setMetaState(ModifierKeyMetaInfo.CTRL_RIGHT, isPressed, oldMetaState); - default: - return oldMetaState; - } - } - - private setMetaState(mask: number, isPressed: boolean, oldMetaState: number): number { - let newMetaState: number; - if (isPressed) { // set the metaState of the pressed modifier key - newMetaState = oldMetaState | mask; - } else { // reset/clear the metaState of all modifier keys (ctrl|alt|shift|win/cmd) - newMetaState = oldMetaState & - ~(mask | ModifierKeyMetaInfo.CTRL | ModifierKeyMetaInfo.ALT | - ModifierKeyMetaInfo.SHIFT | ModifierKeyMetaInfo.META); - } - // Update the non-sided modifier key metaState to match the content of the sided ones. - if (newMetaState & (ModifierKeyMetaInfo.ALT_LEFT | ModifierKeyMetaInfo.ALT_RIGHT)) { - newMetaState |= ModifierKeyMetaInfo.ALT; - } - if (newMetaState & (ModifierKeyMetaInfo.SHIFT_LEFT | ModifierKeyMetaInfo.SHIFT_RIGHT)) { - newMetaState |= ModifierKeyMetaInfo.SHIFT; - } - if (newMetaState & (ModifierKeyMetaInfo.CTRL_LEFT | ModifierKeyMetaInfo.CTRL_RIGHT)) { - newMetaState |= ModifierKeyMetaInfo.CTRL; - } - return newMetaState; - } -} - -/** - * Simulated key event used in soft-keyboard text-editing. - * This class represents a programmatically generated key event, typically used - * for virtual keyboard interactions such as arrow keys, selection keys, or - * other input method editor (IME) operations. Unlike FlutterKeyEvent, this - * does not wrap a hardware KeyEvent and contains only the essential key information. - */ -export class SimulateKeyEvent { - /** The key code value identifying which key was pressed. */ - keyCode: number; - /** The text representation of the key (e.g., "Arrow Up", "Shift"). */ - keyText: string; - /** - * Constructs a new SimulateKeyEvent instance. - * @param keyCode - The key code value (e.g., KeyCode.KEYCODE_DPAD_UP) - * @param keyText - The text representation of the key (e.g., "Arrow Up") - */ - constructor(keyCode: number, keyText: string) { - this.keyCode = keyCode; - this.keyText = keyText; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/KeyboardChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/KeyboardChannel.ets deleted file mode 100644 index 9902e27..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/KeyboardChannel.ets +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021-2025 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - - -import DartExecutor from '../dart/DartExecutor'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import MethodCall from '../../../plugin/common/MethodCall'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; -import Log from '../../../util/Log'; - -/** - * Channel for handling keyboard-related communication between Flutter and OpenHarmony. - * This channel manages keyboard state queries and keyboard method calls. - */ -export default class KeyboardChannel implements MethodCallHandler { - private static TAG = "KeyboardChannel"; - private static CHANNEL_NAME = "flutter/keyboard"; - private channel: MethodChannel; - private handler: KeyboardMethodHandler | null = null; - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult): void { - if (this.handler == null) { - Log.i(KeyboardChannel.TAG, "KeyboardMethodHandler is null"); - return; - } - - let method: string = call.method; - switch (method) { - case "getKeyboardState": { - Log.i(KeyboardChannel.TAG, "getKeyboardState enter"); - result.success(this.handler?.getKeyboardState()); - break; - } - default: { - result.notImplemented(); - break; - } - } - } - - /** - * Constructs a new KeyboardChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new MethodChannel(dartExecutor, KeyboardChannel.CHANNEL_NAME, StandardMethodCodec.INSTANCE); - this.channel.setMethodCallHandler(this); - } - - /** - * Sets the keyboard method handler for processing keyboard-related requests. - * @param keyboardMessageHandler - The KeyboardMethodHandler instance, or null to remove - */ - public setKeyboardMethodHandler(keyboardMessageHandler: KeyboardMethodHandler | null): void { - this.handler = keyboardMessageHandler; - } -} - -/** - * Interface for handling keyboard method calls. - */ -export interface KeyboardMethodHandler { - /** - * Gets the current keyboard state. - * @returns A map containing keyboard state information - */ - getKeyboardState(): Map; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ets deleted file mode 100644 index 98b75ff..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ets +++ /dev/null @@ -1,119 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on LifecycleChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Log from '../../../util/Log'; -import StringCodec from '../../../plugin/common/StringCodec'; -import DartExecutor from '../dart/DartExecutor'; -import BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; - -/** - * Channel for handling application lifecycle events. - * This channel manages communication of lifecycle state changes between OpenHarmony and Flutter, - * including resumed, inactive, paused, and detached states. - */ -export default class LifecycleChannel { - private static TAG = "LifecycleChannel"; - private static CHANNEL_NAME = "flutter/lifecycle"; - // These should stay in sync with the AppLifecycleState enum in the framework. - private static RESUMED = "AppLifecycleState.resumed"; - private static INACTIVE = "AppLifecycleState.inactive"; - private static PAUSED = "AppLifecycleState.paused"; - private static DETACHED = "AppLifecycleState.detached"; - private lastOhosState = ""; - private lastFlutterState = ""; - private lastFocus = true; - private channel: BasicMessageChannel; - - /** - * Constructs a new LifecycleChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new BasicMessageChannel(dartExecutor, LifecycleChannel.CHANNEL_NAME, StringCodec.INSTANCE) - } - - /** - * Called when at least one window in the app has focus. - */ - aWindowIsFocused(): void { - this.sendState(this.lastOhosState, true); - } - - /** - * Called when no windows in the app have focus. - */ - noWindowsAreFocused(): void { - this.sendState(this.lastOhosState, false); - } - - /** - * Called when the app is resumed. - */ - appIsResumed(): void { - this.sendState(LifecycleChannel.RESUMED, this.lastFocus); - } - - /** - * Called when the app is inactive. - */ - appIsInactive(): void { - this.sendState(LifecycleChannel.INACTIVE, this.lastFocus); - } - - /** - * Called when the app is paused. - */ - appIsPaused(): void { - this.sendState(LifecycleChannel.PAUSED, this.lastFocus); - } - - /** - * Called when the app is detached. - */ - appIsDetached(): void { - this.sendState(LifecycleChannel.DETACHED, this.lastFocus); - } - - // Here's the state table this implements: - // - // | UIAbility State | Window focused | Flutter state | - // |-----------------|----------------|---------------| - // | onCreate | true | resumed | - // | onCreate | false | inactive | - // | onForeground | true | resumed | - // | onForeground | false | inactive | - // | onBackground | true | paused | - // | onBackground | false | paused | - // | onDestroy | true | detached | - // | onDestroy | false | detached | - - private sendState(state: string, hasFocus: boolean): void { - if (this.lastOhosState == state && hasFocus == this.lastFocus) { - // No inputs changed, so Flutter state could not have changed. - return; - } - let newState: string; - if (state == LifecycleChannel.RESUMED) { - newState = hasFocus ? LifecycleChannel.RESUMED : LifecycleChannel.INACTIVE; - } else { - newState = state; - } - // Keep the last reported values for future updates. - this.lastOhosState = state; - this.lastFocus = hasFocus; - if (newState == this.lastFlutterState) { - // No change in the resulting Flutter state, so don't report anything. - return; - } - Log.i(LifecycleChannel.TAG, "Sending " + newState + " message."); - this.channel.send(newState); - this.lastFlutterState = newState; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ets deleted file mode 100644 index a8ce802..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ets +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on LocalizationChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import DartExecutor from '../dart/DartExecutor'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import MethodCall from '../../../plugin/common/MethodCall'; -import List from '@ohos.util.List'; -import JSONMethodCodec from '../../../plugin/common/JSONMethodCodec'; -import intl from '@ohos.intl'; -import Log from '../../../util/Log'; - -const TAG = "LocalizationChannel"; - -/** - * Channel for handling localization-related communication between Flutter and OpenHarmony. - * This channel manages locale information and string resource retrieval. - */ -export default class LocalizationChannel implements MethodCallHandler { - private static TAG = "LocalizationChannel"; - private static CHANNEL_NAME = "flutter/localization"; - private channel: MethodChannel; - private localizationMessageHandler: LocalizationMessageHandler | null = null; - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult): void { - if (this.localizationMessageHandler == null) { - Log.e(TAG, "localizationMessageHandler is null"); - return; - } - let method: string = call.method; - switch (method) { - case "Localization.getStringResource": { - Log.i(TAG, "Localization.getStringResource enter"); - let key: string = call.argument("key"); - let localeString: string = ""; - if (call.hasArgument("locale")) { - localeString = call.argument("locale"); - } - result.success(this.localizationMessageHandler?.getStringResource(key, localeString)); - break; - } - default: { - result.notImplemented(); - break; - } - } - } - - /** - * Constructs a new LocalizationChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new MethodChannel(dartExecutor, LocalizationChannel.CHANNEL_NAME, JSONMethodCodec.INSTANCE); - this.channel.setMethodCallHandler(this); - } - - /** - * Sets the localization message handler for processing localization requests. - * @param localizationMessageHandler - The LocalizationMessageHandler instance - */ - setLocalizationMessageHandler(localizationMessageHandler: LocalizationMessageHandler): void { - this.localizationMessageHandler = localizationMessageHandler; - } - - /** - * Sends locale information to Flutter. - * @param locales - Array of locale strings to send - */ - sendLocales(locales: string[]): void { - let data: string[] = []; - for (let i = 0; i < locales.length; i++) { - let locale = new intl.Locale(locales[i]); - data.push(locale.language); - data.push(locale.region); - data.push(locale.script); - data.push(''); // locale.getVariant locale的一种变体 - } - this.channel.invokeMethod("setLocale", data); - } -} - -/** - * Interface for handling localization message requests. - */ -export interface LocalizationMessageHandler { - /** - * Gets a string resource by key and locale. - * @param key - The resource key - * @param local - The locale string - */ - getStringResource(key: string, local: string): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ets deleted file mode 100644 index 9bba2eb..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ets +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MouseCursorChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import HashMap from '@ohos.util.HashMap'; -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; - -const TAG: string = 'MouseCursorChannel'; - -/** - * Channel for handling mouse cursor changes. - * This channel manages communication between Flutter and OpenHarmony for cursor appearance changes. - */ -export default class MouseCursorChannel implements MethodCallHandler { - /** The MethodChannel for mouse cursor communication with Flutter. */ - public channel: MethodChannel; - private mouseCursorMethodHandler: MouseCursorMethodHandler | null = null; - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult): void { - if (this.mouseCursorMethodHandler === null) { - // if no explicit mouseCursorMethodHandler has been registered then we don't - // need to formed this call to an API. Return - Log.e(TAG, "mouseCursorMethodHandler is null") - return; - } - - let method: string = call.method; - Log.i(TAG, "Received '" + method + "' message."); - try { - // More methods are expected to be added here, hence the switch. - switch (method) { - case "activateSystemCursor": - let argument: HashMap = call.args; - let kind: string = argument.get("kind"); - try { - this.mouseCursorMethodHandler.activateSystemCursor(kind); - } catch (err) { - result.error("error", "Error when setting cursors: " + JSON.stringify(err), null); - break; - } - result.success(true); - break; - default: - break; - } - } catch (error) { - result.error("error", "UnHandled error: " + JSON.stringify(error), null) - } - } - - /** - * Constructs a new MouseCursorChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new MethodChannel(dartExecutor, "flutter/mousecursor", StandardMethodCodec.INSTANCE); - this.channel.setMethodCallHandler(this); - } - - /** - * Sets the MouseCursorMethodHandler which receives all events and requests that are - * parsed from the underlying platform channel. - * @param mouseCursorMethodHandler - The MouseCursorMethodHandler instance, or null to remove - */ - public setMethodHandler(mouseCursorMethodHandler: MouseCursorMethodHandler | null): void { - this.mouseCursorMethodHandler = mouseCursorMethodHandler; - } - - /** - * Synthesizes a method call for testing purposes. - * @param call - The method call to synthesize - * @param result - The result callback to send a response - */ - public synthesizeMethodCall(call: MethodCall, result: MethodResult): void { - this.onMethodCall(call, result); - } -} - -/** - * Interface for handling mouse cursor method calls. - */ -export interface MouseCursorMethodHandler { - /** - * Called when the pointer should start displaying a system mouse cursor - * specified by the kind parameter. - * @param kind - The cursor kind/type to activate - */ - activateSystemCursor(kind: String): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NativeVsyncChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NativeVsyncChannel.ets deleted file mode 100644 index 2d20093..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NativeVsyncChannel.ets +++ /dev/null @@ -1,90 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; -import HashMap from '@ohos.util.HashMap'; -import StringUtils from '../../../util/StringUtils'; -import Any from '../../../plugin/common/Any'; -import FlutterNapi from '../FlutterNapi'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import MethodCall from '../../../plugin/common/MethodCall'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; - -/** - * Types of animation voting. - */ -enum AnimationVotingType { - TRANSLATE = 0, - SCALE, - RATATION, -} - -/** - * Channel for handling native VSync functionality. - * This channel manages VSync switching, animation velocity voting, and LTPO switch state checking. - */ -export default class NativeVsyncChannel implements MethodCallHandler { - private static TAG = "NativeVsyncChannel"; - private static CHANNEL_NAME = "flutter/nativevsync"; - /** The MethodChannel for native VSync communication with Flutter. */ - public channel: MethodChannel; - private flutterNapi: FlutterNapi; - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult): void { - let method: string = call.method; - try { - switch (method) { - case "isEnable": - // Whether to enable DVsync - let isEnable: boolean = call.argument('isEnable'); - this.flutterNapi.SetDVsyncSwitch(isEnable); - break; - case "sendVelocity": - // Send animation velocity - let type: string = call.argument('type'); - let velocity: number = call.argument('velocity'); - if (type == "translate") { - FlutterNapi.animationVoting(AnimationVotingType.TRANSLATE, velocity); - } - break; - case "checkLTPOSwtichState": - // Check LTPO switch state - result.success(FlutterNapi.checkLTPOSwitchState()); - break; - default: - break; - } - } catch (error) { - result.error("error", "UnHandled error: " + JSON.stringify(error), null) - } - } - - /** - * Constructs a new NativeVsyncChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - * @param flutterNapi - The FlutterNapi instance for native communication - */ - constructor(dartExecutor: DartExecutor, flutterNapi: FlutterNapi) { - this.channel = new MethodChannel(dartExecutor, NativeVsyncChannel.CHANNEL_NAME, StandardMethodCodec.INSTANCE); - this.channel.setMethodCallHandler(this); - this.flutterNapi = flutterNapi; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ets deleted file mode 100644 index 027f79d..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ets +++ /dev/null @@ -1,241 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on NavigationChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { common } from '@kit.AbilityKit'; -import hiTraceMeter from '@ohos.hiTraceMeter'; - -import JSONMethodCodec from '../../../plugin/common/JSONMethodCodec'; -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; -import FlutterManager from '../../ohos/FlutterManager'; -import Any from '../../../plugin/common/Any'; - -/** - * Navigator activity types. - */ -export enum NavigatorActivity { - PUSH = "push", - POP = "pop", -} - -/** - * Navigator status types. - */ -export enum NavigatorStatus { - START = "start", - FINISH = "finish", -} - -/** - * Channel for handling navigation-related communication between Flutter and OpenHarmony. - * This channel manages route navigation, including setting initial routes, pushing routes, - * and popping routes. - */ -export default class NavigationChannel { - private static TAG = "NavigationChannel"; - private channel: MethodChannel; - - /** - * Constructs a new NavigationChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - * @param context - The application context - */ - constructor(dartExecutor: DartExecutor, context?: common.Context) { - this.channel = new MethodChannel(dartExecutor, "flutter/navigation", JSONMethodCodec.INSTANCE); - // Provide a default handler that returns an empty response to any messages - // on this channel. - this.channel.setMethodCallHandler(new NavigationCallback(dartExecutor, context)); - } - - /** - * Sets the initial route for navigation. - * @param initialRoute - The initial route string - */ - setInitialRoute(initialRoute: string): void { - Log.i(NavigationChannel.TAG, "Sending message to set initial route to '" + initialRoute + "'"); - this.channel.invokeMethod("setInitialRoute", initialRoute); - } - - /** - * Pushes a new route onto the navigation stack. - * @param route - The route string to push - */ - pushRoute(route: string): void { - Log.i(NavigationChannel.TAG, "Sending message to push route '" + route + "'"); - this.channel.invokeMethod("pushRoute", route); - } - - /** - * Pushes route information onto the navigation stack. - * @param route - The route information string to push - */ - pushRouteInformation(route: string): void { - Log.i(NavigationChannel.TAG, "Sending message to push route information '" + route + "'"); - this.channel.invokeMethod("pushRouteInformation", new Map().set("location", route)); - } - - /** - * Pops the current route from the navigation stack. - */ - popRoute(): void { - Log.i(NavigationChannel.TAG, "Sending message to pop route."); - this.channel.invokeMethod("popRoute", null); - } - - /** - * Sets a custom method call handler for navigation events. - * @param handler - The MethodCallHandler to handle navigation method calls - */ - setMethodCallHandler(handler: MethodCallHandler) { - this.channel.setMethodCallHandler(handler); - } -} - -/** - * Default callback handler for navigation channel that returns empty responses. - */ -class NavigationCallback implements MethodCallHandler { - private static TAG = "NavigationChannel"; - private dartExecutor: DartExecutor; - private context?: common.Context; - - constructor(dartExecutor: DartExecutor, context?: common.Context) { - this.dartExecutor = dartExecutor; - this.context = context; - } - - onMethodCall(call: MethodCall, result: MethodResult) { - let method: string = call.method; - let args: Any = call.args; - Log.i(NavigationCallback.TAG, "method = " + method); - switch (method) { - case "reportNavigatorActivity": { - let activity: string = args.get("activity"); - if (activity === undefined) { - Log.e(NavigationCallback.TAG, "reportNavigatorActivity, incorrect parameter activity"); - break; - } - - let status: string = args.get("status"); - if (status === undefined) { - Log.e(NavigationCallback.TAG, "reportNavigatorActivity, incorrect parameter status"); - break; - } - let navigatorStatus: NavigatorStatus = this.getNavigatorStatusFromValue(status); - - let navigatorActivity: NavigatorActivity = this.getNavigatorActivityFromValue(activity); - switch(navigatorActivity) { - case NavigatorActivity.PUSH: - this.reportNavigatorPush(navigatorStatus); - break; - case NavigatorActivity.POP: - this.reportNavigatorPop(navigatorStatus); - break; - } - break; - } - default: { - this.notifyPageChanged(call); - result.success(null); - break; - } - } - } - - private notifyPageChanged(call: MethodCall) { - if (this.dartExecutor == null || this.dartExecutor.flutterNapi == null) { - Log.e(NavigationCallback.TAG, "dartExecutor or flutterNapi is null, cancel OHOS page change notification"); - return; - } - - // Skip notification if context is not provided - if (this.context == null) { - Log.e(NavigationCallback.TAG, "context is not provided, skip OHOS page change notification"); - return; - } - - const argsMap = call.args as Map; - const currentUri: string = argsMap.get('uri') ?? ''; - const currentUriLen: number = currentUri.length; - - // Dynamically get windowId - const windowId: number = FlutterManager.getInstance().getWindowId(this.context); - if (windowId == 0) { - Log.e(NavigationCallback.TAG, "Failed to get windowId, skip OHOS page change notification, uri: " + currentUri); - return; - } - - const notifyResult = this.dartExecutor.flutterNapi.NotifyPageChanged(currentUri, currentUriLen, windowId); - // Return value: 0 means success, non-zero means error - if (notifyResult == 0) { - Log.i(NavigationCallback.TAG, "NotifyPageChanged success, uri: " + currentUri + ", windowId: " + windowId); - } else { - Log.e(NavigationCallback.TAG, "NotifyPageChanged failed, uri: " + currentUri + ", windowId: " + windowId + ", result: " + notifyResult); - } - } - - /** - * Gets a NavigatorActivity from an encoded activity string. - * @param activity - The encoded activity string - * @returns The NavigatorActivity - * @throws Error if the activity string is not recognized - */ - private getNavigatorActivityFromValue(activity: string): NavigatorActivity { - let activityTypes: string[] = [ - NavigatorActivity.PUSH, - NavigatorActivity.POP - ]; - if (activityTypes.includes(activity as NavigatorActivity)) { - return activity as NavigatorActivity; - } - throw new Error("No such NavigatorActivity: " + activity); - } - - /** - * Gets a NavigatorStatus from an encoded status string. - * @param status - The encoded status string - * @returns The NavigatorStatus - * @throws Error if the status string is not recognized - */ - private getNavigatorStatusFromValue(status: string): NavigatorStatus { - let statusTypes: string[] = [ - NavigatorStatus.START, - NavigatorStatus.FINISH - ]; - if (statusTypes.includes(status as NavigatorStatus)) { - return status as NavigatorStatus; - } - throw new Error("No such NavigatorStatus: " + status); - } - - private reportNavigatorPush(navigatorStatus: NavigatorStatus) { - switch(navigatorStatus) { - case NavigatorStatus.START: - hiTraceMeter.startTrace("flutter::NAVIGATOR_PUSH", 0); - break; - case NavigatorStatus.FINISH: - hiTraceMeter.finishTrace("flutter::NAVIGATOR_PUSH", 0); - break; - } - } - - private reportNavigatorPop(navigatorStatus: NavigatorStatus) { - switch(navigatorStatus) { - case NavigatorStatus.START: - hiTraceMeter.startTrace("flutter::NAVIGATOR_POP", 0); - break; - case NavigatorStatus.FINISH: - hiTraceMeter.finishTrace("flutter::NAVIGATOR_POP", 0); - break; - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ets deleted file mode 100644 index 79be8ac..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ets +++ /dev/null @@ -1,674 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import hiTraceMeter from '@ohos.hiTraceMeter'; -import JSONMethodCodec from '../../../plugin/common/JSONMethodCodec'; -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; -import pasteboard from '@ohos.pasteboard'; -import bundleManager from '@ohos.bundle.bundleManager'; -import window from '@ohos.window'; -import Any from '../../../plugin/common/Any'; -import { BusinessError } from '@kit.BasicServicesKit'; -import FlutterNapi from '../FlutterNapi'; -import flutter from 'libflutter.so'; - -/** - * Channel for handling platform-level communication between Flutter and OpenHarmony. - * This channel manages system UI, clipboard, haptic feedback, orientation, and other platform services. - */ -export default class PlatformChannel { - private static TAG = "PlatformChannel"; - private static CHANNEL_NAME = "flutter/platform"; - flutterNapi: FlutterNapi; - /** The MethodChannel for platform-level communication with Flutter. */ - channel: MethodChannel; - /** The platform message handler for processing platform requests, or null if not set. */ - platformMessageHandler: PlatformMessageHandler | null = null; - - /** - * Constructs a new PlatformChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor, flutterNapi: FlutterNapi) { - this.channel = new MethodChannel(dartExecutor, PlatformChannel.CHANNEL_NAME, JSONMethodCodec.INSTANCE); - let callback = new PlatformMethodCallback(this); - this.channel.setMethodCallHandler(callback); - this.flutterNapi = flutterNapi; - } - - /** - * Sets the platform message handler for processing platform requests. - * @param platformMessageHandler - The PlatformMessageHandler instance, or null to remove - */ - setPlatformMessageHandler(platformMessageHandler: PlatformMessageHandler | null): void { - this.platformMessageHandler = platformMessageHandler; - } - - /** - * Notifies Flutter that system chrome overlays have changed. - * @param areOverlaysVisible - Whether system overlays are visible - */ - systemChromeChanged(areOverlaysVisible: boolean): void { - Log.d(PlatformChannel.TAG, "Sending 'systemUIChange' message."); - this.channel.invokeMethod("SystemChrome.systemUIChange", [areOverlaysVisible]); - } - - /** - * Decodes orientation strings into OpenHarmony orientation values. - * @param encodedOrientations - Array of encoded orientation strings - * @returns The decoded orientation value - */ - decodeOrientations(encodedOrientations: string[]): number { - let requestedOrientation = 0x00; - let firstRequestedOrientation = 0x00; - for (let index = 0; index < encodedOrientations.length; index += 1) { - let encodedOrientation = encodedOrientations[index]; - Log.d(PlatformChannel.TAG, "encodedOrientation[" + index + "]: " + encodedOrientation); - let orientation = this.getDeviceOrientationFromValue(encodedOrientation); - switch (orientation) { - case DeviceOrientation.PORTRAIT_UP: - requestedOrientation |= 0x01; - break; - case DeviceOrientation.PORTRAIT_DOWN: - requestedOrientation |= 0x04; - break; - case DeviceOrientation.LANDSCAPE_LEFT: - requestedOrientation |= 0x08; - break; - case DeviceOrientation.LANDSCAPE_RIGHT: - requestedOrientation |= 0x02; - break; - } - if (firstRequestedOrientation == 0x00) { - firstRequestedOrientation = requestedOrientation; - } - } - - switch (requestedOrientation) { - case 0x00: - return window.Orientation.UNSPECIFIED; - case 0x01: - return window.Orientation.PORTRAIT; - case 0x02: - return window.Orientation.LANDSCAPE_INVERTED; - case 0x03: - case 0x04: - return window.Orientation.PORTRAIT_INVERTED; - case 0x05: - return window.Orientation.AUTO_ROTATION_PORTRAIT; - case 0x06: - case 0x07: - case 0x08: - return window.Orientation.LANDSCAPE; - case 0x09: - case 0x0a: - return window.Orientation.AUTO_ROTATION_LANDSCAPE; - case 0x0b: - return window.Orientation.LOCKED; - case 0x0c: - case 0x0d: - case 0x0e: - switch (firstRequestedOrientation) { - case 0x01: - return bundleManager.DisplayOrientation.PORTRAIT; - case 0x02: - return bundleManager.DisplayOrientation.LANDSCAPE_INVERTED; - case 0x04: - return bundleManager.DisplayOrientation.PORTRAIT_INVERTED; - case 0x08: - return bundleManager.DisplayOrientation.LANDSCAPE; - } - case 0x0f: - return window.Orientation.AUTO_ROTATION_RESTRICTED; - } - return bundleManager.DisplayOrientation.PORTRAIT; - } - - /** - * Gets a HapticFeedbackType from an encoded name. - * @param encodedName - The encoded feedback type name - * @returns The HapticFeedbackType, or STANDARD if not found - */ - getFeedbackTypeFromValue(encodedName: string): HapticFeedbackType { - if (encodedName == null) { - return HapticFeedbackType.STANDARD; - } - let feedbackTypes: string[] = [ - HapticFeedbackType.STANDARD, - HapticFeedbackType.LIGHT_IMPACT, - HapticFeedbackType.MEDIUM_IMPACT, - HapticFeedbackType.HEAVY_IMPACT, - HapticFeedbackType.SELECTION_CLICK - ]; - if (feedbackTypes.includes(encodedName as HapticFeedbackType)) { - return encodedName as HapticFeedbackType; - } else { - Log.e(PlatformChannel.TAG, "No such HapticFeedbackType:" + encodedName); - return HapticFeedbackType.STANDARD; - } - } - - /** - * Gets a ClipboardContentFormat from an encoded name. - * @param encodedName - The encoded format name - * @returns The ClipboardContentFormat, or PLAIN_TEXT if not found - */ - getClipboardContentFormatFromValue(encodedName: string): ClipboardContentFormat { - let clipboardFormats: string[] = [ClipboardContentFormat.PLAIN_TEXT]; - if (clipboardFormats.includes(encodedName as ClipboardContentFormat)) { - return encodedName as ClipboardContentFormat; - } - return ClipboardContentFormat.PLAIN_TEXT; - } - - /** - * Gets a SystemUiOverlay from an encoded name. - * @param encodedName - The encoded overlay name - * @returns The SystemUiOverlay - * @throws Error if the overlay name is not recognized - */ - getSystemUiOverlayFromValue(encodedName: string): SystemUiOverlay { - let systemUiOverlays: string[] = [SystemUiOverlay.TOP_OVERLAYS, SystemUiOverlay.BOTTOM_OVERLAYS]; - if (systemUiOverlays.includes(encodedName as SystemUiOverlay)) { - return encodedName as SystemUiOverlay; - } - throw new Error("No such SystemUiOverlay: " + encodedName); - } - - /** - * Gets a SystemUiMode from an encoded name. - * @param encodedName - The encoded mode name - * @returns The SystemUiMode - * @throws Error if the mode name is not recognized - */ - getSystemUiModeFromValue(encodedName: string): SystemUiMode { - let systemUiModes: string[] = [ - SystemUiMode.LEAN_BACK, SystemUiMode.IMMERSIVE, - SystemUiMode.IMMERSIVE_STICKY, SystemUiMode.EDGE_TO_EDGE - ]; - if (systemUiModes.includes(encodedName as SystemUiMode)) { - return encodedName as SystemUiMode; - } - throw new Error("No such SystemUiOverlay: " + encodedName); - } - - /** - * Gets a Brightness from an encoded name. - * @param encodedName - The encoded brightness name - * @returns The Brightness - * @throws Error if the brightness name is not recognized - */ - getBrightnessFromValue(encodedName: string): Brightness { - let brightnesses: string[] = [Brightness.LIGHT, Brightness.DARK]; - if (brightnesses.includes(encodedName as Brightness)) { - return encodedName as Brightness; - } - throw new Error("No such Brightness: " + encodedName); - } - - /** - * Gets a DeviceOrientation from an encoded name. - * @param encodedName - The encoded orientation name - * @returns The DeviceOrientation - * @throws Error if the orientation name is not recognized - */ - getDeviceOrientationFromValue(encodedName: string): DeviceOrientation { - let deviceOrientations: DeviceOrientation[] = [ - DeviceOrientation.PORTRAIT_UP, DeviceOrientation.PORTRAIT_DOWN, - DeviceOrientation.LANDSCAPE_LEFT, DeviceOrientation.LANDSCAPE_RIGHT - ]; - if (deviceOrientations.includes(encodedName as DeviceOrientation)) { - return encodedName as DeviceOrientation; - } - throw new Error("No such DeviceOrientation: " + encodedName); - } - - /** - * Gets a ScrollActivity from an encoded activity string. - * @param activity - The encoded activity string - * @returns The ScrollActivity - * @throws Error if the activity string is not recognized - */ - getScrollActivityFromValue(activity: string): ScrollActivity { - let activityTypes: string[] = [ - ScrollActivity.START, - ScrollActivity.END - ]; - if (activityTypes.includes(activity as ScrollActivity)) { - return activity as ScrollActivity; - } - throw new Error("No such ScrollActivity: " + activity); - } - -} - -/** - * Types of haptic feedback. - */ -export enum HapticFeedbackType { - STANDARD = "STANDARD", - LIGHT_IMPACT = "HapticFeedbackType.lightImpact", - MEDIUM_IMPACT = "HapticFeedbackType.mediumImpact", - HEAVY_IMPACT = "HapticFeedbackType.heavyImpact", - SELECTION_CLICK = "HapticFeedbackType.selectionClick" -} - -/** - * Interface for handling platform message requests from Flutter. - */ -export interface PlatformMessageHandler { - playSystemSound(soundType: SoundType): void; - - vibrateHapticFeedback(feedbackType: HapticFeedbackType): Promise; - - setPreferredOrientations(ohosOrientation: number, result: MethodResult): void; - - setApplicationSwitcherDescription(description: AppSwitcherDescription): void; - - showSystemOverlays(overlays: SystemUiOverlay[]): void; - - showSystemUiMode(mode: SystemUiMode): void; - - setSystemUiChangeListener(): void; - - restoreSystemUiOverlays(): void; - - setSystemUiOverlayStyle(systemUiOverlayStyle: SystemChromeStyle): void; - - popSystemNavigator(): void; - - getClipboardData(result: MethodResult): void; - - setClipboardData(text: string, result: MethodResult): void; - - clipboardHasStrings(): boolean; -} - -/** - * Clipboard content formats. - */ -export enum ClipboardContentFormat { - PLAIN_TEXT = "text/plain", -} - -/** - * System sound types. - */ -export enum SoundType { - CLICK = "SystemSoundType.click", - ALERT = "SystemSoundType.alert", -} - -/** - * Description for the app switcher. - */ -export class AppSwitcherDescription { - /** The color value for the app switcher. */ - public readonly color: number; - /** The label text for the app switcher. */ - public readonly label: string; - - /** - * Constructs a new AppSwitcherDescription instance. - * @param color - The color value - * @param label - The label text - */ - constructor(color: number, label: string) { - this.color = color; - this.label = label; - } -} - -/** - * System UI overlay positions. - */ -export enum SystemUiOverlay { - TOP_OVERLAYS = "SystemUiOverlay.top", - BOTTOM_OVERLAYS = "SystemUiOverlay.bottom", -} - -/** - * System UI display modes. - */ -export enum SystemUiMode { - LEAN_BACK = "SystemUiMode.leanBack", - IMMERSIVE = "SystemUiMode.immersive", - IMMERSIVE_STICKY = "SystemUiMode.immersiveSticky", - EDGE_TO_EDGE = "SystemUiMode.edgeToEdge", -} - -export enum Brightness { - LIGHT = "Brightness.light", - DARK = "Brightness.dark", -} - -/** - * Style configuration for system chrome (status bar and navigation bar). - */ -export class SystemChromeStyle { - /** The status bar color, or null if not set. */ - public readonly statusBarColor: number | null; - /** The status bar icon brightness, or null if not set. */ - public readonly statusBarIconBrightness: Brightness | null; - /** Whether status bar contrast is enforced, or null if not set. */ - public readonly systemStatusBarContrastEnforced: boolean | null; - /** The navigation bar color, or null if not set. */ - public readonly systemNavigationBarColor: number | null; - /** The navigation bar icon brightness, or null if not set. */ - public readonly systemNavigationBarIconBrightness: Brightness | null; - /** The navigation bar divider color, or null if not set. */ - public readonly systemNavigationBarDividerColor: number | null; - /** Whether navigation bar contrast is enforced, or null if not set. */ - public readonly systemNavigationBarContrastEnforced: boolean | null; - - /** - * Constructs a new SystemChromeStyle instance. - * @param statusBarColor - The status bar color - * @param statusBarIconBrightness - The status bar icon brightness - * @param systemStatusBarContrastEnforced - Whether status bar contrast is enforced - * @param systemNavigationBarColor - The navigation bar color - * @param systemNavigationBarIconBrightness - The navigation bar icon brightness - * @param systemNavigationBarDividerColor - The navigation bar divider color - * @param systemNavigationBarContrastEnforced - Whether navigation bar contrast is enforced - */ - constructor(statusBarColor: number | null, - statusBarIconBrightness: Brightness | null, - systemStatusBarContrastEnforced: boolean | null, - systemNavigationBarColor: number | null, - systemNavigationBarIconBrightness: Brightness | null, - systemNavigationBarDividerColor: number | null, - systemNavigationBarContrastEnforced: boolean | null) { - this.statusBarColor = statusBarColor; - this.statusBarIconBrightness = statusBarIconBrightness; - this.systemStatusBarContrastEnforced = systemStatusBarContrastEnforced; - this.systemNavigationBarColor = systemNavigationBarColor; - this.systemNavigationBarIconBrightness = systemNavigationBarIconBrightness; - this.systemNavigationBarDividerColor = systemNavigationBarDividerColor; - this.systemNavigationBarContrastEnforced = systemNavigationBarContrastEnforced; - } -} - -/** - * Device orientation types. - */ -export enum DeviceOrientation { - PORTRAIT_UP = "DeviceOrientation.portraitUp", - PORTRAIT_DOWN = "DeviceOrientation.portraitDown", - LANDSCAPE_LEFT = "DeviceOrientation.landscapeLeft", - LANDSCAPE_RIGHT = "DeviceOrientation.landscapeRight", -} - -/** - * Scroll activity types. - */ -export enum ScrollActivity { - START = "start", - END = "end", -} - -enum AnimationStatus { - SCROLL_START = 0, - SCROLL_END = 1, -} - -/** - * Method call handler for platform channel requests. - */ -class PlatformMethodCallback implements MethodCallHandler { - private static TAG = "PlatformMethodCallback" - platform: PlatformChannel; - private scrollType: string | null = null; - - /** - * Constructs a new PlatformMethodCallback instance. - * @param platform - The PlatformChannel instance - */ - constructor(platform: PlatformChannel) { - this.platform = platform; - } - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult) { - if (this.platform.platformMessageHandler == null) { - Log.w(PlatformMethodCallback.TAG, "platformMessageHandler is null"); - return; - } - - let method: string = call.method; - let args: Any = call.args; - Log.d(PlatformMethodCallback.TAG, "Received '" + method + "' message."); - try { - switch (method) { - case "SystemSound.play": - break; - case "HapticFeedback.vibrate": - try { - Log.d(PlatformMethodCallback.TAG, "HapticFeedback: " + args as string); - let feedbackType = this.platform.getFeedbackTypeFromValue(args as string); - this.platform.platformMessageHandler.vibrateHapticFeedback(feedbackType) - .then(() => { - result.success(null); - }) - .catch((e: BusinessError) => { - Log.e(PlatformMethodCallback.TAG, `HapticFeedback.vibrate error: ${e.code} - ${e.message}`); - }); - } catch (e) { - Log.e(PlatformMethodCallback.TAG, "HapticFeedback.vibrate error:" + JSON.stringify(e)); - } - break; - case "SystemChrome.setPreferredOrientations": - Log.d(PlatformMethodCallback.TAG, "setPreferredOrientations: " + JSON.stringify(args)); - let ohosOrientation = this.platform.decodeOrientations(args as string[]); - this.platform.platformMessageHandler.setPreferredOrientations(ohosOrientation, result); - break; - case "SystemChrome.setApplicationSwitcherDescription": - Log.d(PlatformMethodCallback.TAG, "setApplicationSwitcherDescription: " + JSON.stringify(args)); - try { - let description: AppSwitcherDescription = this.decodeAppSwitcherDescription(args); - this.platform.platformMessageHandler.setApplicationSwitcherDescription(description); - result.success(null); - } catch (err) { - Log.e(PlatformMethodCallback.TAG, "setApplicationSwitcherDescription err:" + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - } - break; - case "SystemChrome.setEnabledSystemUIOverlays": - try { - let overlays: SystemUiOverlay[] = this.decodeSystemUiOverlays(args); - Log.d(PlatformMethodCallback.TAG, "overlays: " + overlays); - this.platform.platformMessageHandler.showSystemOverlays(overlays); - result.success(null); - } catch (err) { - Log.e(PlatformMethodCallback.TAG, "setEnabledSystemUIOverlays err:" + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - } - break; - case "SystemChrome.setEnabledSystemUIMode": - try { - Log.d(PlatformMethodCallback.TAG, "setEnabledSystemUIMode args:" + args as string); - let mode: SystemUiMode = this.decodeSystemUiMode(args as string) - this.platform.platformMessageHandler.showSystemUiMode(mode); - } catch (err) { - Log.e(PlatformMethodCallback.TAG, "setEnabledSystemUIMode err:" + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - } - break; - case "SystemChrome.setSystemUIChangeListener": - this.platform.platformMessageHandler.setSystemUiChangeListener(); - result.success(null); - break; - case "SystemChrome.restoreSystemUIOverlays": - this.platform.platformMessageHandler.restoreSystemUiOverlays(); - result.success(null); - break; - case "SystemChrome.setSystemUIOverlayStyle": - try { - Log.d(PlatformMethodCallback.TAG, "setSystemUIOverlayStyle asrgs: " + JSON.stringify(args)); - let systemChromeStyle: SystemChromeStyle = this.decodeSystemChromeStyle(args); - this.platform.platformMessageHandler.setSystemUiOverlayStyle(systemChromeStyle); - result.success(null); - } catch (err) { - Log.e(PlatformMethodCallback.TAG, "setSystemUIOverlayStyle err:" + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - } - break; - case "SystemNavigator.pop": - this.platform.platformMessageHandler.popSystemNavigator(); - result.success(null); - break; - case "Clipboard.getData": - this.platform.platformMessageHandler.getClipboardData(result); - break; - case "Clipboard.setData": - let clipboardContent: string = args.get('text'); - this.platform.platformMessageHandler.setClipboardData(clipboardContent, result); - break; - case "Clipboard.hasStrings": - let response: Any = new Map().set("value", false); - let systemPasteboard = pasteboard.getSystemPasteboard(); - systemPasteboard.hasData().then((hasData) => { - response.set("value", hasData); - result.success(response); - }).catch((err: Any) => { - Log.e(PlatformMethodCallback.TAG, "systemPasteboard.hasData err: " + JSON.stringify(err)); - }) - break; - case "Scroll.Activity": - /// Report the behavior of scrolling components. - /// The optional values for Scroll.Activity include [start] and [end]. - this.recordScrollActivity(args as string); - break; - case "Scroll.type": - /// Report the type of the scrolling component. - /// Track scrollable widget names to identify [_PagePosition] instances. - let type: string = args.get("type"); - this.recordTabSwitch(type); - break; - default: - result.notImplemented(); - break; - } - } catch (e) { - result.error("error", JSON.stringify(e), null); - } - } - - private decodeAppSwitcherDescription(encodedDescription: Map): AppSwitcherDescription { - let color: number = encodedDescription.get('color') as number; - let label: string = encodedDescription.get('label') as string; - return new AppSwitcherDescription(color, label); - } - - private decodeSystemUiOverlays(encodedSystemUiOverlay: string[]): SystemUiOverlay[] { - let overlays: SystemUiOverlay[] = []; - for (let i = 0; i < encodedSystemUiOverlay.length; i++) { - const encodedOverlay = encodedSystemUiOverlay[i]; - const overlay = this.platform.getSystemUiOverlayFromValue(encodedOverlay); - switch (overlay) { - case SystemUiOverlay.TOP_OVERLAYS: - overlays.push(SystemUiOverlay.TOP_OVERLAYS); - break; - case SystemUiOverlay.BOTTOM_OVERLAYS: - overlays.push(SystemUiOverlay.BOTTOM_OVERLAYS); - break; - } - } - return overlays; - } - - private decodeSystemUiMode(encodedSystemUiMode: string): SystemUiMode { - let mode: SystemUiMode = this.platform.getSystemUiModeFromValue(encodedSystemUiMode); - switch (mode) { - case SystemUiMode.LEAN_BACK: - return SystemUiMode.LEAN_BACK; - case SystemUiMode.IMMERSIVE: - return SystemUiMode.IMMERSIVE; - case SystemUiMode.IMMERSIVE_STICKY: - return SystemUiMode.IMMERSIVE_STICKY; - case SystemUiMode.EDGE_TO_EDGE: - default: - return SystemUiMode.EDGE_TO_EDGE; - } - } - - private decodeSystemChromeStyle(encodedStyle: Map | null): SystemChromeStyle { - let statusBarColor: number | null = null; - let statusBarIconBrightness: Brightness | null = null; - let systemStatusBarContrastEnforced: boolean | null = null; - let systemNavigationBarColor: number | null = null; - let systemNavigationBarIconBrightness: Brightness | null = null; - let systemNavigationBarDividerColor: number | null = null; - let systemNavigationBarContrastEnforced: boolean | null = null; - if (encodedStyle?.get('statusBarColor') != null) { - statusBarColor = encodedStyle.get('statusBarColor') as number; - } - if (encodedStyle?.get('statusBarIconBrightness') != null) { - statusBarIconBrightness = - this.platform.getBrightnessFromValue(encodedStyle.get('statusBarIconBrightness') as string); - } - if (encodedStyle?.get('systemStatusBarContrastEnforced') != null) { - systemStatusBarContrastEnforced = encodedStyle.get('systemStatusBarContrastEnforced') as boolean; - } - if (encodedStyle?.get('systemNavigationBarColor') != null) { - systemNavigationBarColor = encodedStyle.get('systemNavigationBarColor') as number; - } - if (encodedStyle?.get('systemNavigationBarIconBrightness') != null) { - systemNavigationBarIconBrightness = - this.platform.getBrightnessFromValue(encodedStyle.get('systemNavigationBarIconBrightness') as string); - } - if (encodedStyle?.get('systemNavigationBarDividerColor') != null) { - systemNavigationBarDividerColor = encodedStyle.get('systemNavigationBarDividerColor') as number; - } - if (encodedStyle?.get('systemNavigationBarContrastEnforced') != null) { - systemNavigationBarContrastEnforced = encodedStyle.get('systemNavigationBarContrastEnforced') as boolean; - } - return new SystemChromeStyle( - statusBarColor, - statusBarIconBrightness, - systemStatusBarContrastEnforced, - systemNavigationBarColor, - systemNavigationBarIconBrightness, - systemNavigationBarDividerColor, - systemNavigationBarContrastEnforced - ); - } - - private recordScrollActivity(scrollActivity: string) { - let activityType = this.platform.getScrollActivityFromValue(scrollActivity); - switch(activityType) { - case ScrollActivity.START: - hiTraceMeter.startTrace('flutter::APP_LIST_FLING', 0); - this.platform.flutterNapi.SetAnimationStatus(AnimationStatus.SCROLL_START); - break; - case ScrollActivity.END: - hiTraceMeter.finishTrace('flutter::APP_LIST_FLING', 0); - if (this.scrollType !== null) { - this.scrollType = null; - hiTraceMeter.finishTrace('flutter::TABVIEW_SWITCH', 0); - } - this.platform.flutterNapi.SetAnimationStatus(AnimationStatus.SCROLL_END); - break; - } - } - - private recordTabSwitch(type: string) { - if (type == '_PagePosition') { - hiTraceMeter.startTrace('flutter::TABVIEW_SWITCH', 0); - this.scrollType = type; - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/PlatformViewsChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/PlatformViewsChannel.ets deleted file mode 100644 index 2f761a8..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/PlatformViewsChannel.ets +++ /dev/null @@ -1,679 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformViewsChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import Any from '../../../plugin/common/Any'; - -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; -import { ByteBuffer } from '../../../util/ByteBuffer'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; - -const TAG = "PlatformViewsChannel"; -const NON_TEXTURE_FALLBACK = -2; - -/** - * System channel that sends 2-way communication between Flutter and OpenHarmony to facilitate - * embedding of OpenHarmony Views within a Flutter application. - * - * Implement PlatformViewsHandler and register it via setPlatformViewsHandler() to implement - * the OpenHarmony side of this channel. - */ -export default class PlatformViewsChannel { - private channel: MethodChannel; - private handler: PlatformViewsHandler | null = null; - private parsingHandler = new ParsingCallback(); - - /** - * Constructs a PlatformViewsChannel that connects OpenHarmony to the Dart - * code running in dartExecutor. - * - * The given dartExecutor is permitted to be idle or executing code. - * - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new MethodChannel(dartExecutor, "flutter/platform_views", StandardMethodCodec.INSTANCE); - this.parsingHandler.platformChannel = this; - this.channel.setMethodCallHandler(this.parsingHandler); - } - - /** - * Sets the PlatformViewsHandler which receives all events and requests that are parsed - * from the underlying platform views channel. - * @param handler - The PlatformViewsHandler instance, or null to remove - */ - public setPlatformViewsHandler(handler: PlatformViewsHandler | null): void { - this.handler = handler; - this.parsingHandler.handler = handler; - } - - /** - * Notifies Flutter that a platform view has gained focus. - * @param viewId - The ID of the platform view that gained focus - */ - public invokeViewFocused(viewId: number): void { - if (this.channel == null) { - return; - } - this.channel.invokeMethod("viewFocused", viewId); - } - - /** - * Handles platform view creation requests. - * @param call - The method call containing creation parameters - * @param result - The result callback to send a response - */ - create(call: MethodCall, result: MethodResult): void { - const createArgs: Map = call.args; - const usesPlatformViewLayer: boolean = createArgs.has("hybrid") && createArgs.get("hybrid") as boolean; - const additionalParams: ByteBuffer = createArgs.has("params") ? createArgs.get("params") : null; - - let direction: Direction = Direction.Ltr; - if (createArgs.get("direction") == 0) { - direction = Direction.Ltr; - } else if (createArgs.get("direction") == 1) { - direction = Direction.Rtl; - } - - try { - if (usesPlatformViewLayer) { - const request: PlatformViewCreationRequest = new PlatformViewCreationRequest( - createArgs.get("id"), - createArgs.get("viewType"), - 0, - 0, - 0, - 0, - direction, - additionalParams, - RequestedDisplayMode.HYBRID_ONLY - ); - this.handler?.createForPlatformViewLayer(request); - result.success(null); - } else { - const hybridFallback: boolean = createArgs.has("hybridFallback") && createArgs.get("hybridFallback"); - const displayMode: RequestedDisplayMode = - hybridFallback ? RequestedDisplayMode.TEXTURE_WITH_HYBRID_FALLBACK - : RequestedDisplayMode.TEXTURE_WITH_VIRTUAL_FALLBACK; - const request: PlatformViewCreationRequest = new PlatformViewCreationRequest( - createArgs.get("id"), - createArgs.get("viewType"), - createArgs.has("top") ? createArgs.get("top") : 0.0, - createArgs.has("left") ? createArgs.get("left") : 0.0, - createArgs.get("width"), - createArgs.get("height"), - direction, - additionalParams, - displayMode - ); - - Log.i(TAG, `Create texture param id:${request.viewId}, - type:${request.viewType}, - w:${request.logicalWidth}, - h:${request.logicalHeight}, - l:${request.logicalLeft}, - t:${request.logicalTop}, - d:${request.direction}`); - - const textureId = this.handler?.createForTextureLayer(request); - if (textureId == NON_TEXTURE_FALLBACK) { - if (!hybridFallback) { - throw new Error( - "Platform view attempted to fall back to hybrid mode when not requested."); - } - - // A fallback to hybrid mode is indicated with a null texture ID. - result.success(null); - } else { - result.success(textureId); - } - } - } catch (err) { - Log.e(TAG, "create failed" + err); - result.error("error", err, null); - } - } - - /** - * Handles platform view disposal requests. - * @param call - The method call containing the view ID to dispose - * @param result - The result callback to send a response - */ - dispose(call: MethodCall, result: MethodResult): void { - const disposeArgs: Map = call.args; - const viewId: number = disposeArgs.get("id"); - try { - this.handler?.dispose(viewId); - result.success(null); - } catch (err) { - Log.e(TAG, "dispose failed", err); - result.error("error", err, null); - } - } - - /** - * Handles platform view resize requests. - * @param call - The method call containing resize parameters - * @param result - The result callback to send a response - */ - resize(call: MethodCall, result: MethodResult): void { - const resizeArgs: Map = call.args; - const resizeRequest: PlatformViewResizeRequest = new PlatformViewResizeRequest( - resizeArgs.get("id"), - resizeArgs.get("width"), - resizeArgs.get("height") - ); - try { - let resizeCallback = new ResizeCallback(); - resizeCallback.result = result; - this.handler?.resize(resizeRequest, resizeCallback); - } catch (err) { - Log.e(TAG, "resize failed", err); - result.error("error", err, null); - } - } - - /** - * Handles platform view offset change requests. - * @param call - The method call containing offset parameters - * @param result - The result callback to send a response - */ - offset(call: MethodCall, result: MethodResult): void { - const offsetArgs: Map = call.args; - try { - this.handler?.offset( - offsetArgs.get("id"), - offsetArgs.get("top"), - offsetArgs.get("left")); - result.success(null); - } catch (err) { - Log.e(TAG, "offset failed", err); - result.error("error", err, null); - } - } - - /** - * Handles touch events on platform views. - * @param call - The method call containing touch event data - * @param result - The result callback to send a response - */ - touch(call: MethodCall, result: MethodResult): void { - const args: Array = call.args; - let index = 0; - const touch: PlatformViewTouch = new PlatformViewTouch( - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index++], - args[index] - ); - - try { - this.handler?.onTouch(touch); - result.success(null); - } catch (err) { - Log.e(TAG, "offset failed", err); - result.error("error", err, null); - } - } - - /** - * Handles platform view direction change requests. - * @param call - The method call containing direction parameters - * @param result - The result callback to send a response - */ - setDirection(call: MethodCall, result: MethodResult): void { - const setDirectionArgs: Map = call.args; - const newDirectionViewId: number = setDirectionArgs.get("id"); - const direction: number = setDirectionArgs.get("direction"); - - try { - this.handler?.setDirection(newDirectionViewId, direction); - result.success(null); - } catch (err) { - Log.e(TAG, "setDirection failed", err); - result.error("error", err, null); - } - } - - /** - * Handles platform view focus clearing requests. - * @param call - The method call containing the view ID - * @param result - The result callback to send a response - */ - clearFocus(call: MethodCall, result: MethodResult): void { - const viewId: number = call.args; - try { - this.handler?.clearFocus(viewId); - result.success(null); - } catch (err) { - Log.e(TAG, "clearFocus failed", err); - result.error("error", err, null); - } - } - - /** - * Handles requests to synchronize to native view hierarchy. - * @param call - The method call containing synchronization parameters - * @param result - The result callback to send a response - */ - synchronizeToNativeViewHierarchy(call: MethodCall, result: MethodResult): void { - const yes: boolean = call.args; - try { - this.handler?.synchronizeToNativeViewHierarchy(yes); - result.success(null); - } catch (err) { - Log.e(TAG, "synchronizeToNativeViewHierarchy failed", err); - result.error("error", err, null); - } - } - - /** - * Handles hover events on platform views. - * @param call - The method call containing the view ID - * @param result - The result callback to send a response - */ - hover(call: MethodCall, result: MethodResult) { - const viewId: number = call.args; - try { - this.handler?.hover(viewId); - result.success(null); - } catch (err) { - Log.e(TAG, "hover failed", err); - result.error("error", err, null); - } - } -} - -/** - * Handler that receives platform view messages sent from Flutter to OpenHarmony through a given - * PlatformViewsChannel. - * - * To register a PlatformViewsHandler with a PlatformViewsChannel, - * see PlatformViewsChannel.setPlatformViewsHandler. - */ -export interface PlatformViewsHandler { - /** - * The Flutter application would like to display a new OpenHarmony View, i.e., platform view. - * - * The OpenHarmony View is added to the view hierarchy. This view is rendered in the Flutter - * framework by a PlatformViewLayer. - * - * @param request - The metadata sent from the framework - */ - createForPlatformViewLayer(request: PlatformViewCreationRequest): void; - - /** - * The Flutter application would like to display a new OpenHarmony View, i.e., platform view. - * - * The OpenHarmony View is added to the view hierarchy. This view is rendered in the Flutter - * framework by a TextureLayer. - * - * The ID returned by createForTextureLayer to indicate that the requested texture mode - * was not available and the view creation fell back to PlatformViewLayer mode. - * This can only be returned if the PlatformViewCreationRequest sets - * TEXTURE_WITH_HYBRID_FALLBACK as the requested display mode. - * - * @param request - The metadata sent from the framework - * @returns The texture ID, or NON_TEXTURE_FALLBACK if falling back to hybrid mode - */ - createForTextureLayer(request: PlatformViewCreationRequest): number; - - /** - * The Flutter application would like to dispose of an existing OpenHarmony View. - * @param viewId - The ID of the platform view to dispose - */ - dispose(viewId: number): void; - - /** - * The Flutter application would like to resize an existing OpenHarmony View. - * - * @param request - The request to resize the platform view - * @param onComplete - Once the resize is completed, this is the handler to notify the size of the - * platform view buffer - */ - resize(request: PlatformViewResizeRequest, onComplete: PlatformViewBufferResized): void; - - /** - * The Flutter application would like to change the offset of an existing OpenHarmony View. - * @param viewId - The ID of the platform view - * @param top - The new top offset - * @param left - The new left offset - */ - offset(viewId: number, top: number, left: number): void; - - /** - * The user touched a platform view within Flutter. - * - * Touch data is reported in the touch parameter. - * @param touch - The touch event data - */ - onTouch(touch: PlatformViewTouch): void; - - /** - * The Flutter application would like to change the layout direction of an existing OpenHarmony - * View, i.e., platform view. - * @param viewId - The ID of the platform view - * @param direction - The new layout direction - */ - setDirection(viewId: number, direction: Direction): void; - - /** - * Clears the focus from the platform view with a given id if it is currently focused. - * @param viewId - The ID of the platform view - */ - clearFocus(viewId: number): void; - - /** - * Whether the render surface of FlutterView should be converted to a - * FlutterImageView when a PlatformView is added. - * - * This is done to synchronize the rendering of the PlatformView and the FlutterView. Defaults - * to true. - * @param yes - Whether to synchronize to native view hierarchy - */ - synchronizeToNativeViewHierarchy(yes: boolean): void; - - /** - * Handles hover events on a platform view. - * @param viewId - The ID of the platform view - */ - hover(viewId: number): void; -} - -/** Platform view display modes that can be requested at creation time. */ -enum RequestedDisplayMode { - /** Use Texture Layer if possible, falling back to Virtual Display if not. */ - TEXTURE_WITH_VIRTUAL_FALLBACK, - /** Use Texture Layer if possible, falling back to Hybrid Composition if not. */ - TEXTURE_WITH_HYBRID_FALLBACK, - /** Use Hybrid Composition in all cases. */ - HYBRID_ONLY, -} - -/** Request sent from Flutter to create a new platform view. */ -export class PlatformViewCreationRequest { - /** The ID of the platform view as seen by the Flutter side. */ - public viewId: number; - /** The type of view to create for this platform view. */ - public viewType: string; - /** The density independent width to display the platform view. */ - public logicalWidth: number; - /** The density independent height to display the platform view. */ - public logicalHeight: number; - /** The density independent top position to display the platform view. */ - public logicalTop: number; - /** The density independent left position to display the platform view. */ - public logicalLeft: number; - /** The layout direction of the new platform view. */ - public direction: Direction; - /** The requested display mode for the platform view. */ - public displayMode: RequestedDisplayMode; - /** Custom parameters that are unique to the desired platform view. */ - public params: ByteBuffer; - - /** - * Constructs a new PlatformViewCreationRequest instance. - * @param viewId - The ID of the platform view - * @param viewType - The type of view to create - * @param logicalTop - The density-independent top position - * @param logicalLeft - The density-independent left position - * @param logicalWidth - The density-independent width - * @param logicalHeight - The density-independent height - * @param direction - The layout direction - * @param params - Custom parameters for the view - * @param displayMode - The requested display mode (optional) - */ - constructor(viewId: number, viewType: string, logicalTop: number, logicalLeft: number, logicalWidth: number, - logicalHeight: number, direction: Direction, params: ByteBuffer, displayMode?: RequestedDisplayMode) { - this.viewId = viewId; - this.viewType = viewType; - this.logicalTop = logicalTop; - this.logicalLeft = logicalLeft; - this.logicalWidth = logicalWidth; - this.logicalHeight = logicalHeight; - this.direction = direction; - this.displayMode = displayMode ? displayMode : RequestedDisplayMode.TEXTURE_WITH_VIRTUAL_FALLBACK; - this.params = params; - } -} - -/** Request sent from Flutter to resize a platform view. */ -export class PlatformViewResizeRequest { - /** The ID of the platform view as seen by the Flutter side. */ - public viewId: number; - /** The new density independent width to display the platform view. */ - public newLogicalWidth: number; - /** The new density independent height to display the platform view. */ - public newLogicalHeight: number; - - /** - * Constructs a new PlatformViewResizeRequest instance. - * @param viewId - The ID of the platform view - * @param newLogicalWidth - The new density-independent width - * @param newLogicalHeight - The new density-independent height - */ - constructor(viewId: number, newLogicalWidth: number, newLogicalHeight: number) { - this.viewId = viewId; - this.newLogicalWidth = newLogicalWidth; - this.newLogicalHeight = newLogicalHeight; - } -} - -/** The platform view buffer size. */ -export class PlatformViewBufferSize { - /** The width of the screen buffer. */ - public width: number; - /** The height of the screen buffer. */ - public height: number; - - /** - * Constructs a new PlatformViewBufferSize instance. - * @param width - The width of the buffer - * @param height - The height of the buffer - */ - constructor(width: number, height: number) { - this.width = width; - this.height = height; - } -} - -/** Allows to notify when a platform view buffer has been resized. */ -export abstract class PlatformViewBufferResized { - /** - * Called when the platform view buffer has been resized. - * @param bufferSize - The new buffer size - */ - abstract run(bufferSize: PlatformViewBufferSize): void; -} - -/** The state of a touch event in Flutter within a platform view. */ -export class PlatformViewTouch { - /** The ID of the platform view as seen by the Flutter side. */ - public viewId: number; - /** The amount of time that the touch has been pressed. */ - public downTime: number; - /** The time when the event occurred. */ - public eventTime: number; - /** The touch action type. */ - public action: number; - /** The number of pointers (e.g, fingers) involved in the touch event. */ - public pointerCount: number; - /** Properties for each pointer, encoded in a raw format. */ - public rawPointerPropertiesList: Any; - /** Coordinates for each pointer, encoded in a raw format. */ - public rawPointerCoords: Any; - /** The meta state indicating modifier keys pressed. */ - public metaState: number; - /** The button state indicating which buttons are pressed. */ - public buttonState: number; - /** Coordinate precision along the x-axis. */ - public xPrecision: number; - /** Coordinate precision along the y-axis. */ - public yPrecision: number; - /** The ID of the input device that generated the event. */ - public deviceId: number; - /** Edge flags indicating which screen edges were touched. */ - public edgeFlags: number; - /** The event source indicating the type of input device. */ - public source: number; - /** Event flags providing additional information about the event. */ - public flags: number; - /** The motion event ID for tracking the event. */ - public motionEventId: number; - - /** - * Constructs a new PlatformViewTouch instance. - * @param viewId - The ID of the platform view - * @param downTime - The time when the touch was pressed - * @param eventTime - The time of the event - * @param action - The touch action - * @param pointerCount - The number of pointers - * @param rawPointerPropertiesList - Raw pointer properties - * @param rawPointerCoords - Raw pointer coordinates - * @param metaState - The meta state - * @param buttonState - The button state - * @param xPrecision - X-axis coordinate precision - * @param yPrecision - Y-axis coordinate precision - * @param deviceId - The device ID - * @param edgeFlags - Edge flags - * @param source - The event source - * @param flags - Event flags - * @param motionEventId - The motion event ID - */ - constructor(viewId: number, - downTime: number, - eventTime: number, - action: number, - pointerCount: number, - rawPointerPropertiesList: Any, - rawPointerCoords: Any, - metaState: number, - buttonState: number, - xPrecision: number, - yPrecision: number, - deviceId: number, - edgeFlags: number, - source: number, - flags: number, - motionEventId: number) { - this.viewId = viewId; - this.downTime = downTime; - this.eventTime = eventTime; - this.action = action; - this.pointerCount = pointerCount; - this.rawPointerPropertiesList = rawPointerPropertiesList; - this.rawPointerCoords = rawPointerCoords; - this.metaState = metaState; - this.buttonState = buttonState; - this.xPrecision = xPrecision; - this.yPrecision = yPrecision; - this.deviceId = deviceId; - this.edgeFlags = edgeFlags; - this.source = source; - this.flags = flags; - this.motionEventId = motionEventId; - } -} - -/** - * Method call handler for parsing platform view requests. - */ -class ParsingCallback implements MethodCallHandler { - platformChannel: PlatformViewsChannel | null = null; - handler: PlatformViewsHandler | null = null; - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult) { - if (this.handler == null) { - return; - } - - Log.i(TAG, "Received '" + call.method + "' message."); - switch (call.method) { - case "create": { - this.platformChannel?.create(call, result); - break; - } - case "dispose": { - this.platformChannel?.dispose(call, result); - break; - } - case "resize": { - this.platformChannel?.resize(call, result); - break; - } - case "offset": { - this.platformChannel?.offset(call, result); - break; - } - case "touch": { - this.platformChannel?.touch(call, result); - break; - } - case "setDirection": { - this.platformChannel?.setDirection(call, result); - break; - } - case "clearFocus": { - this.platformChannel?.clearFocus(call, result); - break; - } - case "synchronizeToNativeViewHierarchy": { - this.platformChannel?.synchronizeToNativeViewHierarchy(call, result); - break; - } - case "hover": { - this.platformChannel?.hover(call, result); - break; - } - default: - result.notImplemented(); - } - } -} - -/** - * Callback for handling platform view resize completion. - */ -class ResizeCallback extends PlatformViewBufferResized { - result: MethodResult | null = null; - - /** - * Called when the resize is complete. - * @param bufferSize - The new buffer size - */ - run(bufferSize: PlatformViewBufferSize) { - if (bufferSize == null) { - this.result?.error("error", "Failed to resize the platform view", null); - } else { - const response: Map = new Map(); - response.set("width", bufferSize.width); - response.set("height", bufferSize.height); - this.result?.success(response); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ets deleted file mode 100644 index b2978c6..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ets +++ /dev/null @@ -1,206 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on RestorationChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import Any from '../../../plugin/common/Any'; - -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; -import Log from '../../../util/Log'; -import StringUtils from '../../../util/StringUtils'; -import DartExecutor from '../dart/DartExecutor'; - -/** - * System channel to exchange restoration data between framework and engine. - * - * The engine can obtain the current restoration data from the framework via this channel to - * store it on disk and - when the app is relaunched - provide the stored data back to the framework - * to recreate the original state of the app. - * - * The channel can be configured to delay responding to the framework's request for restoration - * data via waitForRestorationData until the engine-side has provided the data. This is - * useful when the engine is pre-warmed at a point in the application's life cycle where the - * restoration data is not available yet. For example, if the engine is pre-warmed as part of the - * Application before an Ability is created, this flag should be set to true because OpenHarmony will - * only provide the restoration data to the Ability during the onCreate callback. - * - * The current restoration data provided by the framework can be read via getRestorationData. - */ -export default class RestorationChannel { - private static TAG = "RestorationChannel"; - private static CHANNEL_NAME = "flutter/restoration"; - /** - * Whether the channel delays responding to the framework's initial request for restoration data - * until setRestorationData has been called. - * - * If the engine never calls setRestorationData this flag must be set to false. If set - * to true, the engine must call setRestorationData either with the actual restoration - * data as argument or null if it turns out that there is no restoration data. - * - * If the response to the framework's request for restoration data is not delayed until the - * data has been set via setRestorationData, the framework may intermittently initialize - * itself to default values until the restoration data has been made available. Setting this flag - * to true avoids that extra work. - */ - public waitForRestorationData: boolean = false; - /** Pending framework restoration channel request waiting for restoration data. */ - public pendingFrameworkRestorationChannelRequest: MethodResult | null = null; - /** Whether the engine has provided restoration data. */ - public engineHasProvidedData: boolean = false; - /** Whether the framework has requested restoration data. */ - public frameworkHasRequestedData: boolean = false; - private restorationData: Uint8Array; - private channel: MethodChannel | null = null; - private handler: MethodCallHandler; - - /** - * Constructs a new RestorationChannel instance. - * @param channelOrExecutor - Either a MethodChannel or DartExecutor instance - * @param waitForRestorationData - Whether to wait for restoration data before responding - */ - constructor(channelOrExecutor: MethodChannel | DartExecutor, waitForRestorationData: boolean) { - if (channelOrExecutor instanceof MethodChannel) { - this.channel = channelOrExecutor; - } else { - this.channel = - new MethodChannel(channelOrExecutor, RestorationChannel.CHANNEL_NAME, StandardMethodCodec.INSTANCE); - } - this.waitForRestorationData = waitForRestorationData; - this.restorationData = new Uint8Array(1).fill(0); - this.handler = new RestorationChannelMethodCallHandler(this); - this.channel.setMethodCallHandler(this.handler); - } - - /** - * Gets the most current restoration data that the framework has provided. - * @returns The restoration data as a Uint8Array - */ - getRestorationData(): Uint8Array { - return this.restorationData; - } - - /** - * Sets the restoration data without sending it to the framework. - * @param data - The restoration data to set - */ - setRestorationDataOnly(data: Uint8Array) { - this.restorationData = data; - } - - /** - * Sets the restoration data from which the framework will restore its state. - * @param data - The restoration data to set - */ - setRestorationData(data: Uint8Array) { - this.engineHasProvidedData = true; - if (this.pendingFrameworkRestorationChannelRequest != null) { - // If their is a pending request from the framework, answer it. - this.pendingFrameworkRestorationChannelRequest.success(RestorationChannelMethodCallHandler.packageData(data)); - this.pendingFrameworkRestorationChannelRequest = null; - this.restorationData = data; - } else if (this.frameworkHasRequestedData) { - // If the framework has previously received the engine's restoration data, push the new data - // directly to it. This case can happen when "waitForRestorationData" is false and the - // framework retrieved the restoration state before it was set via this method. - // Experimentally, this can also be used to restore a previously used engine to another state, - // e.g. when the engine is attached to a new activity. - this.channel?.invokeMethod( - "push", RestorationChannelMethodCallHandler.packageData(data), { - success: (result: Any): void => { - this.restorationData = data; - }, - - error: (errorCode: string, errorMessage: string, errorDetails: Any): void => { - Log.e( - RestorationChannel.TAG, - "Error " + errorCode + " while sending restoration data to framework: " + errorMessage - ); - }, - - notImplemented: (): void => { - // do nothing - } - }) - } else { - // Otherwise, just cache the data until the framework asks for it. - this.restorationData = data; - } - } - - /** - * Clears the current restoration data. - * - * This should be called just prior to a hot restart. Otherwise, after the hot restart the - * state prior to the hot restart will get restored. - */ - clearData() { - this.restorationData = new Uint8Array(1).fill(0); - } -} - -/** - * Method call handler for the restoration channel. - */ -class RestorationChannelMethodCallHandler implements MethodCallHandler { - private channel: RestorationChannel; - - /** - * Constructs a new RestorationChannelMethodCallHandler instance. - * @param channel - The RestorationChannel instance - */ - constructor(channel: RestorationChannel) { - this.channel = channel; - } - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult): void { - const method = call.method; - const args: Any = call.args; - switch (method) { - case "put": { - this.channel.setRestorationDataOnly(args); - result.success(null); - break; - } - case "get": { - this.channel.frameworkHasRequestedData = true; - if (this.channel.engineHasProvidedData || !this.channel.waitForRestorationData) { - result.success(RestorationChannelMethodCallHandler.packageData(this.channel.getRestorationData())); - // Do not delete the restoration data on the engine side after sending it to the - // framework. We may need to hand this data back to the operating system if the - // framework never modifies the data (and thus doesn't send us any - // data back). - } else { - this.channel.pendingFrameworkRestorationChannelRequest = result; - } - break; - } - default: { - result.notImplemented(); - break; - } - } - } - - /** - * Packages restoration data into a message format. - * @param data - The restoration data to package - * @returns A map containing the packaged restoration data - */ - static packageData(data: Uint8Array): Map { - const packaged: Map = new Map(); - packaged.set("enabled", true); - packaged.set("data", data); - return packaged; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SensitiveContentChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SensitiveContentChannel.ets deleted file mode 100644 index 55241d0..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SensitiveContentChannel.ets +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2026 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -import Any from '../../../plugin/common/Any'; -import DartExecutor from '../dart/DartExecutor'; -import StandardMethodCodec from '../../../plugin/common/StandardMethodCodec'; -import Log from '../../../util/Log'; -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; - -const TAG = "SensitiveContentChannel"; - -export const SENSITIVE_CONTENT_SENSITIVITY = 1; -export const NOT_SENSITIVE_CONTENT_SENSITIVITY = 2; - -export default class SensitiveContentChannel implements MethodCallHandler { - private static CHANNEL_NAME = "flutter/sensitivecontent"; - private channel: MethodChannel; - private sensitiveContentMethodHandler: SensitiveContentMethodHandler | null = null; - - constructor(dartExecutor: DartExecutor) { - this.channel = new MethodChannel(dartExecutor, SensitiveContentChannel.CHANNEL_NAME, StandardMethodCodec.INSTANCE); - this.channel.setMethodCallHandler(this); - } - - onMethodCall(call: MethodCall, result: MethodResult): void { - if (this.sensitiveContentMethodHandler == null) { - // No SensitiveContentChannel registered, call not forwarded to sensitive content API. - return; - } - let method: string = call.method; - Log.d(TAG, "Received '" + method + "' message."); - switch (method) { - case "SensitiveContent.setContentSensitivity": - this.setContentSensitivity(call, result); - break; - case "SensitiveContent.getContentSensitivity": - this.getContentSensitivity(call, result); - break; - case "SensitiveContent.isSupported": - this.isSupported(call, result); - break; - default: - Log.d(TAG, "Method " + method + " is not implemented for the SensitiveContentChannel."); - result.notImplemented(); - break; - } - } - - private setContentSensitivity(call: MethodCall, result: MethodResult): void { - try { - const contentSensitivityLevel: number = call.args as number; - const deserializedValue = this.deserializeContentSensitivity(contentSensitivityLevel); - this.sensitiveContentMethodHandler!.setContentSensitivity(deserializedValue); - result.success(null); - } catch (error) { - result.error("error", (error as Error).message, null); - } - } - - private getContentSensitivity(call: MethodCall, result: MethodResult): void { - try { - const currentContentSensitivity: number = this.sensitiveContentMethodHandler!.getContentSensitivity(); - const serializedValue = this.serializeContentSensitivity(currentContentSensitivity); - result.success(serializedValue); - } catch (error) { - result.error("error", (error as Error).message, null); - } - } - - private isSupported(call: MethodCall, result: MethodResult): void { - const isSupported: boolean = this.sensitiveContentMethodHandler!.isSupported(); - result.success(isSupported); - } - - /** - * Deserializes Flutter content sensitivity index to native value. - */ - private deserializeContentSensitivity(contentSensitivityIndex: number): number { - switch (contentSensitivityIndex) { - case NOT_SENSITIVE_CONTENT_SENSITIVITY: - return NOT_SENSITIVE_CONTENT_SENSITIVITY; - case SENSITIVE_CONTENT_SENSITIVITY: - return SENSITIVE_CONTENT_SENSITIVITY; - default: - throw new Error( - "contentSensitivityIndex " + contentSensitivityIndex + " not known to the SensitiveContentChannel." - ); - } - } - - /** - * Serializes native content sensitivity value to Flutter index. - */ - private serializeContentSensitivity(contentSensitivityValue: number): number { - switch (contentSensitivityValue) { - case NOT_SENSITIVE_CONTENT_SENSITIVITY: - return NOT_SENSITIVE_CONTENT_SENSITIVITY; - case SENSITIVE_CONTENT_SENSITIVITY: - return SENSITIVE_CONTENT_SENSITIVITY; - default: - return NOT_SENSITIVE_CONTENT_SENSITIVITY; - } - } - - /** - * Sets the {@link SensitiveContentMethodHandler} which receives all requests to get and set a - * particular content sensitivity level sent through this channel. - */ - setSensitiveContentMethodHandler(sensitiveContentMethodHandler: SensitiveContentMethodHandler | null): void { - this.sensitiveContentMethodHandler = sensitiveContentMethodHandler; - } -} - -export interface SensitiveContentMethodHandler { - /** - * Requests that a native Flutter OpenHarmony View sets its content sensitivity level to - * {@code requestedContentSensitivity}. - */ - setContentSensitivity(requestedContentSensitivity: number): void; - - /** - * Returns the current content sensitivity level of a Flutter OpenHarmony View. - */ - getContentSensitivity(): number; - - /** - * Returns whether or not setting/getting content sensitivity via OpenHarmony APIs is supported on - * the device. - */ - isSupported(): boolean; -} - diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ets deleted file mode 100644 index bd84d1a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ets +++ /dev/null @@ -1,145 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on SettingsChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; -import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; - -/** - * Platform brightness modes. - */ -export enum PlatformBrightness { - LIGHT = "light", - DARK = "dark" -} - -const TAG = "SettingsChannel"; -const TEXT_SCALE_FACTOR = "textScaleFactor"; -const NATIVE_SPELL_CHECK_SERVICE_DEFINED = "nativeSpellCheckServiceDefined"; -const BRIEFLY_SHOW_PASSWORD = "brieflyShowPassword"; -const ALWAYS_USE_24_HOUR_FORMAT = "alwaysUse24HourFormat"; -const PLATFORM_BRIGHTNESS = "platformBrightness"; - -/** - * Channel for sending system settings to Flutter. - * This channel manages settings such as text scale factor, platform brightness, and other system preferences. - */ -export default class SettingsChannel { - private static CHANNEL_NAME = "flutter/settings"; - private channel: BasicMessageChannel; - - /** - * Constructs a new SettingsChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = - new BasicMessageChannel(dartExecutor, SettingsChannel.CHANNEL_NAME, JSONMessageCodec.INSTANCE); - } - - /** - * Starts building a settings message. - * @returns A MessageBuilder instance for constructing the settings message - */ - startMessage(): MessageBuilder { - return new MessageBuilder(this.channel); - } -} - -/** - * Builder for constructing settings messages to send to Flutter. - */ -class MessageBuilder { - private channel: BasicMessageChannel; - private settingsMessage: Map = new Map([ - [TEXT_SCALE_FACTOR, 1.0], - [NATIVE_SPELL_CHECK_SERVICE_DEFINED, false], - [BRIEFLY_SHOW_PASSWORD, false], - [ALWAYS_USE_24_HOUR_FORMAT, false], - [PLATFORM_BRIGHTNESS, PlatformBrightness.LIGHT] - ]); - - /** - * Constructs a new MessageBuilder instance. - * @param channel - The BasicMessageChannel to send messages through - */ - constructor(channel: BasicMessageChannel) { - this.channel = channel; - } - - /** - * Sets the text scale factor. - * @param textScaleFactor - The text scale factor value - * @returns This MessageBuilder instance for method chaining - */ - setTextScaleFactor(textScaleFactor: Number): MessageBuilder { - this.settingsMessage.set(TEXT_SCALE_FACTOR, textScaleFactor); - return this; - } - - /** - * Sets whether native spell check service is defined. - * @param nativeSpellCheckServiceDefined - Whether the service is defined - * @returns This MessageBuilder instance for method chaining - */ - setNativeSpellCheckServiceDefined(nativeSpellCheckServiceDefined: boolean): MessageBuilder { - this.settingsMessage.set(NATIVE_SPELL_CHECK_SERVICE_DEFINED, nativeSpellCheckServiceDefined); - return this; - } - - /** - * Sets whether to briefly show password. - * @param brieflyShowPassword - Whether to briefly show password - * @returns This MessageBuilder instance for method chaining - */ - setBrieflyShowPassword(brieflyShowPassword: boolean): MessageBuilder { - this.settingsMessage.set(BRIEFLY_SHOW_PASSWORD, brieflyShowPassword); - return this; - } - - /** - * Sets whether to always use 24-hour format. - * @param alwaysUse24HourFormat - Whether to always use 24-hour format - * @returns This MessageBuilder instance for method chaining - */ - setAlwaysUse24HourFormat(alwaysUse24HourFormat: boolean): MessageBuilder { - this.settingsMessage.set(ALWAYS_USE_24_HOUR_FORMAT, alwaysUse24HourFormat); - return this; - } - - /** - * Sets the platform brightness. - * @param platformBrightness - The platform brightness mode - * @returns This MessageBuilder instance for method chaining - */ - setPlatformBrightness(platformBrightness: PlatformBrightness): MessageBuilder { - this.settingsMessage.set(PLATFORM_BRIGHTNESS, platformBrightness); - return this; - } - - /** - * Sends the constructed settings message to Flutter. - */ - send(): void { - Log.i(TAG, "Sending message: " - + TEXT_SCALE_FACTOR + " : " - + this.settingsMessage.get(TEXT_SCALE_FACTOR) - + ", " + NATIVE_SPELL_CHECK_SERVICE_DEFINED + " : " - + this.settingsMessage.get(NATIVE_SPELL_CHECK_SERVICE_DEFINED) - + ", " + BRIEFLY_SHOW_PASSWORD + " : " - + this.settingsMessage.get(BRIEFLY_SHOW_PASSWORD) - + ", " + ALWAYS_USE_24_HOUR_FORMAT + " : " - + this.settingsMessage.get(ALWAYS_USE_24_HOUR_FORMAT) - + ", " + PLATFORM_BRIGHTNESS + " : " - + this.settingsMessage.get(PLATFORM_BRIGHTNESS)); - this.channel.send(this.settingsMessage) - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/StatusBarClickChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/StatusBarClickChannel.ets deleted file mode 100644 index 4cef4dc..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/StatusBarClickChannel.ets +++ /dev/null @@ -1,21 +0,0 @@ -/* -* Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_HW file. -*/ - -import DartExecutor from '../dart/DartExecutor'; -import { BinaryMessenger } from '../../../plugin/common/BinaryMessenger'; -import MethodChannel from '../../../plugin/common/MethodChannel'; - -export default class StatusBarClickChannel { - private static CHANNEL_NAME = "flutter/statusBarClick"; - private channel: MethodChannel; - - constructor(binaryMessenger: BinaryMessenger) { - this.channel = new MethodChannel(binaryMessenger, StatusBarClickChannel.CHANNEL_NAME); - } - sendClick() { - this.channel.invokeMethod('onClick', null); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SystemChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SystemChannel.ets deleted file mode 100644 index c600c7d..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/SystemChannel.ets +++ /dev/null @@ -1,43 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on SystemChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; -import Any from '../../../plugin/common/Any'; -import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; - -const TAG: string = "SystemChannel"; - -/** - * System channel for communicating system-level events between OpenHarmony and Flutter. - * This channel handles events such as memory pressure warnings. - */ -export default class SystemChannel { - /** The BasicMessageChannel for sending system-level messages to Flutter. */ - public channel: BasicMessageChannel; - - /** - * Constructs a new SystemChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new BasicMessageChannel(dartExecutor, "flutter/system", JSONMessageCodec.INSTANCE); - } - - /** - * Sends a memory pressure warning to the Flutter framework. - */ - public sendMemoryPressureWarning(): void { - Log.i(TAG, "Sending memory pressure warning to Flutter"); - let message: Map = new Map().set("type", "memoryPressure"); - this.channel.send(message); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/TestChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/TestChannel.ets deleted file mode 100644 index d9c1777..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/TestChannel.ets +++ /dev/null @@ -1,45 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import BasicMessageChannel, { MessageHandler, Reply } from '../../../plugin/common/BasicMessageChannel'; -import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; -import DartExecutor from '../dart/DartExecutor'; -import Log from '../../../util/Log'; - -const TAG = "TestChannel" - -/** - * Test channel for Flutter testing purposes. - * This channel provides a simple echo mechanism for testing message passing. - */ -export default class TestChannel { - private channel: BasicMessageChannel; - - /** - * Constructs a new TestChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new BasicMessageChannel(dartExecutor, "flutter/test", JSONMessageCodec.INSTANCE); - let callback = new MessageCallback(); - this.channel.setMessageHandler(callback); - } -} - -/** - * Message callback handler for the test channel. - */ -class MessageCallback implements MessageHandler { - /** - * Handles incoming messages and echoes them back. - * @param message - The message received from Dart - * @param reply - The reply callback to send a response - */ - onMessage(message: string, reply: Reply) { - Log.d(TAG, "receive msg = " + message); - reply.reply("收到消息啦:" + message); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ets deleted file mode 100644 index 89948f5..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ets +++ /dev/null @@ -1,810 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on TextInputChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import JSONMethodCodec from '../../../plugin/common/JSONMethodCodec'; -import MethodCall from '../../../plugin/common/MethodCall'; -import MethodChannel, { MethodCallHandler, MethodResult } from '../../../plugin/common/MethodChannel'; -import TextInputPlugin from '../../../plugin/editing/TextInputPlugin'; -import Log from '../../../util/Log'; -import DartExecutor from '../dart/DartExecutor'; -import inputMethod from '@ohos.inputMethod'; -import ArrayList from '@ohos.util.ArrayList'; -import { TextEditingDelta, TextEditingDeltaJson } from '../../../plugin/editing/TextEditingDelta'; -import Any from '../../../plugin/common/Any'; -import { display } from '@kit.ArkUI' -import { window } from '@kit.ArkUI'; -import { BusinessError, print } from '@kit.BasicServicesKit'; -import { PointerDeviceKind } from '../../ohos/OhosTouchProcessor'; - -const TAG = "TextInputChannel"; -/// 规避换行标识无法显示问题,api修改后再删除 -const NEWLINE_KEY_TYPE: number = 8; - -/** - * TextInputChannel is a platform channel between OpenHarmony and Flutter that is used to - * communicate information about the user's text input. - * - * When the user presses an action button like "done" or "next", that action is sent from - * OpenHarmony to Flutter through this TextInputChannel. - * - * When an input system in the Flutter app wants to show the keyboard, or hide it, or configure - * editing state, etc. a message is sent from Flutter to OpenHarmony through this TextInputChannel. - * - * TextInputChannel comes with a default MethodChannel.MethodCallHandler that parses incoming - * messages from Flutter. Implement TextInputMethodHandler and register it via - * setTextInputMethodHandler() to respond to standard Flutter text input messages. - */ -export default class TextInputChannel { - private static CHANNEL_NAME = "flutter/textinput"; - /** The MethodChannel for text input communication with Flutter. */ - public channel: MethodChannel; - /** The text input method handler for processing text input requests, or null if not set. */ - textInputMethodHandler: TextInputMethodHandler | null = null; - private TextInputCallback: TextInputCallback | null = null; - - /** - * Constructs a new TextInputChannel instance. - * @param dartExecutor - The DartExecutor for sending messages to Dart - */ - constructor(dartExecutor: DartExecutor) { - this.channel = new MethodChannel(dartExecutor, TextInputChannel.CHANNEL_NAME, JSONMethodCodec.INSTANCE); - } - - /** - * Sets the text input method handler. - * @param textInputMethodHandler - The TextInputMethodHandler instance, or null to remove - */ - setTextInputMethodHandler(textInputMethodHandler: TextInputMethodHandler | null): void { - this.textInputMethodHandler = textInputMethodHandler; - this.TextInputCallback = this.textInputMethodHandler == null - ? null : new TextInputCallback(this.textInputMethodHandler); - this.channel.setMethodCallHandler(this.TextInputCallback); - } - - /** - * Requests the existing input state from Flutter. - */ - requestExistingInputState(): void { - this.channel.invokeMethod("TextInputClient.requestExistingInputState", null); - } - - /** - * Creates an editing state JSON object. - * @param text - The text content - * @param selectionStart - The start of the selection - * @param selectionEnd - The end of the selection - * @param composingStart - The start of the composing region - * @param composingEnd - The end of the composing region - * @returns The editing state object - */ - createEditingStateJSON(text: string, - selectionStart: number, - selectionEnd: number, - composingStart: number, - composingEnd: number): EditingState { - let state: EditingState = { - text: text, - selectionBase: selectionStart, - selectionExtent: selectionEnd, - composingBase: composingStart, - composingExtent: composingEnd - }; - return state; - } - - /** - * Creates an editing delta JSON object from a batch of deltas. - * @param batchDeltas - Array list of text editing deltas - * @returns The editing delta object - */ - createEditingDeltaJSON(batchDeltas: ArrayList): EditingDelta { - let deltas: TextEditingDeltaJson[] = []; - batchDeltas.forEach((val, idx, array) => { - deltas.push(val.toJSON()); - }) - - let state: EditingDelta = { - deltas: deltas, - }; - return state; - } - - /** - * Instructs Flutter to update its text input editing state to reflect the given configuration. - */ - updateEditingState(inputClientId: number, - text: string, - selectionStart: number, - selectionEnd: number, - composingStart: number, - composingEnd: number): void { - Log.d(TAG, "updateEditingState:" - + "Text: " + text + " Selection start: " + selectionStart + " Selection end: " - + selectionEnd + " Composing start: " + composingStart + " Composing end: " + composingEnd); - const state: Any = this.createEditingStateJSON(text, selectionStart, selectionEnd, composingStart, composingEnd); - this.channel.invokeMethod('TextInputClient.updateEditingState', [inputClientId, state]); - } - - /** - * Updates the editing state with deltas. - * @param inputClientId - The input client ID - * @param batchDeltas - Array list of text editing deltas - */ - updateEditingStateWithDeltas(inputClientId: number, batchDeltas: ArrayList): void { - Log.d(TAG, "updateEditingStateWithDeltas:" + "batchDeltas length: " + batchDeltas.length); - if(batchDeltas.length > 0){ - const state: Any = this.createEditingDeltaJSON(batchDeltas); - this.channel.invokeMethod('TextInputClient.updateEditingStateWithDeltas', [inputClientId, state]); - } - } - - /** - * Sends a newline action to Flutter. - * @param inputClientId - The input client ID - */ - newline(inputClientId: number): void { - Log.d(TAG, "Sending 'newline' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.newline"]); - } - - /** - * Sends a go action to Flutter. - * @param inputClientId - The input client ID - */ - go(inputClientId: number): void { - Log.d(TAG, "Sending 'go' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.go"]); - } - - /** - * Sends a search action to Flutter. - * @param inputClientId - The input client ID - */ - search(inputClientId: number): void { - Log.d(TAG, "Sending 'search' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.search"]); - } - - /** - * Sends a send action to Flutter. - * @param inputClientId - The input client ID - */ - send(inputClientId: number): void { - Log.d(TAG, "Sending 'send' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.send"]); - } - - /** - * Sends a done action to Flutter. - * @param inputClientId - The input client ID - */ - done(inputClientId: number): void { - Log.d(TAG, "Sending 'done' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.done"]); - } - - /** - * Sends a next action to Flutter. - * @param inputClientId - The input client ID - */ - next(inputClientId: number): void { - Log.d(TAG, "Sending 'next' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.next"]); - } - - /** - * Sends a previous action to Flutter. - * @param inputClientId - The input client ID - */ - previous(inputClientId: number): void { - Log.d(TAG, "Sending 'previous' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.previous"]); - } - - /** - * Sends an unspecified action to Flutter. - * @param inputClientId - The input client ID - */ - unspecifiedAction(inputClientId: number): void { - Log.d(TAG, "Sending 'unspecifiedAction' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.unspecified"]); - } - - /** - * Sends a commit content action to Flutter. - * @param inputClientId - The input client ID - */ - commitContent(inputClientId: number): void { - Log.d(TAG, "Sending 'commitContent' message."); - this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.commitContent"]); - } - - /** - * Notifies Flutter that the input connection has been closed. - * @param inputClientId - The input client ID - */ - onConnectionClosed(inputClientId: number): void { - Log.d(TAG, "Sending 'onConnectionClosed' message."); - this.channel.invokeMethod("TextInputClient.onConnectionClosed", [inputClientId]); - this.textInputMethodHandler?.hide(); - } - - /** - * Performs a private command. - * @param inputClientId - The input client ID - * @param action - The action string - * @param data - The command data - */ - performPrivateCommand(inputClientId: number, action: string, data: Any) { - - } - - /** - * Sets the window position for text input. - * @param windowPosition - The window position rectangle - */ - public setWindowPosition(windowPosition: window.Rect) { - this.TextInputCallback?.setWindowPosition(windowPosition); - this.TextInputCallback?.setCursorPosition(); - } - - /** - * Sets the device pixel ratio. - * @param devicePixelRatio - The device pixel ratio value - */ - public setDevicePixelRatio(devicePixelRatio: number) { - this.TextInputCallback?.setDevicePixelRatio(devicePixelRatio); - } - - /** - * Gets the keyboard focus state. - * @returns Whether the keyboard has focus - */ - getKeyboardFocusState() { - return this.textInputMethodHandler?.getKeyboardFocusState(); - } - -} - -/** - * Editing state interface. - */ -interface EditingState { - text: string; - selectionBase: number; - selectionExtent: number; - composingBase: number; - composingExtent: number; -} - - -/** - * Editing delta interface. - */ -interface EditingDelta { - deltas: Array; -} - - -/** - * Interface for handling text input method operations. - */ -export interface TextInputMethodHandler { - show(): void; - - hide(): void; - - requestAutofill(): void; - - finishAutofillContext(shouldSave: boolean): void; - - setClient(textInputClientId: number, configuration: Configuration | null): void; - - updateConfig(configuration: Configuration | null): void; - - setPlatformViewClient(id: number, usesVirtualDisplay: boolean): void; - - setEditableSizeAndTransform(width: number, height: number, transform: number[]): void; - - setCursorSizeAndPosition(cursorInfo: inputMethod.CursorInfo): void; - - setEditingState(editingState: TextEditState): void; - - clearClient(): void; - - handleChangeFocus(focusState: boolean): void; - - getKeyboardFocusState(): boolean; - -} - -/** - * A text editing configuration. - */ -export class Configuration { - /** Whether text should be obscured (e.g., for password fields). */ - obscureText: boolean = false; - /** Whether autocorrect is enabled. */ - autocorrect: boolean = false; - /** Whether autofill is enabled. */ - autofill: boolean = false; - /** Whether suggestions are enabled. */ - enableSuggestions: boolean = false; - /** Whether IME personalized learning is enabled. */ - enableIMEPersonalizedLearning: boolean = false; - /** Whether delta model is enabled for text editing. */ - enableDeltaModel: boolean = false; - /** The input type for the text field, or null if not set. */ - inputType: InputType | null = null; - /** The input action to perform when the user submits. */ - inputAction: Number = 0; - /** The label for the action button. */ - actionLabel: String = ""; - /** MIME types for content commit operations. */ - contentCommitMimeTypes: String[] = []; - /** The kind of pointer device being used. */ - deviceKind: PointerDeviceKind = PointerDeviceKind.UNKNOWN; - /** Array of field configurations for autofill. */ - fields: Configuration[] = []; - - /** - * Constructs a new Configuration instance. - * @param obscureText - Whether text should be obscured - * @param autocorrect - Whether autocorrect is enabled - * @param enableSuggestions - Whether suggestions are enabled - * @param enableIMEPersonalizedLearning - Whether IME personalized learning is enabled - * @param enableDeltaModel - Whether delta model is enabled - * @param inputType - The input type - * @param inputAction - The input action - * @param actionLabel - The action label - * @param autofill - Whether autofill is enabled - * @param contentListString - Content commit MIME types - * @param deviceKind - The pointer device kind - * @param fields - Array of field configurations - */ - constructor(obscureText: boolean, - autocorrect: boolean, - enableSuggestions: boolean, - enableIMEPersonalizedLearning: boolean, - enableDeltaModel: boolean, - inputType: InputType, - inputAction: Number, - actionLabel: String, - autofill: boolean, - contentListString: [], - deviceKind: PointerDeviceKind, - fields: Configuration[] - ) { - this.obscureText = obscureText; - this.autocorrect = autocorrect; - this.enableSuggestions = enableSuggestions; - this.enableIMEPersonalizedLearning = enableIMEPersonalizedLearning; - this.enableDeltaModel = enableDeltaModel; - this.inputType = inputType; - this.inputAction = inputAction; - this.actionLabel = actionLabel; - this.autofill = autofill; - this.contentCommitMimeTypes = contentListString; - this.fields = fields - this.deviceKind = deviceKind - } - - private static inputActionFromTextInputAction(inputActionName: string): number { - switch (inputActionName) { - case "TextInputAction.previous": - return inputMethod.EnterKeyType.PREVIOUS - case "TextInputAction.unspecified": - return inputMethod.EnterKeyType.UNSPECIFIED - case "TextInputAction.none": - return inputMethod.EnterKeyType.NONE - case "TextInputAction.go": - return inputMethod.EnterKeyType.GO - case "TextInputAction.search": - return inputMethod.EnterKeyType.SEARCH - case "TextInputAction.send": - return inputMethod.EnterKeyType.SEND - case "TextInputAction.next": - return inputMethod.EnterKeyType.NEXT - case "TextInputAction.newline": - return NEWLINE_KEY_TYPE - case "TextInputAction.done": - return inputMethod.EnterKeyType.DONE - default: - // Present default key if bad input type is given. - return inputMethod.EnterKeyType.UNSPECIFIED - } - } - - /** - * Creates a Configuration instance from JSON. - * @param json - The JSON object - * @returns A new Configuration instance - */ - static fromJson(json: Any) { - const inputActionName: string = json.inputAction; - if (!inputActionName) { - throw new Error("Configuration JSON missing 'inputAction' property."); - } - - let fields: Array = new Array(); - if (json.fields !== null && json.fields !== undefined) { - fields = json.fields.map((field: Any): Any => Configuration.fromJson(field)); - } - - const inputAction: number = Configuration.inputActionFromTextInputAction(inputActionName); - - // Build list of content commit mime types from the data in the JSON list. - const contentList: Array = []; - if (json.contentCommitMimeTypes !== null && json.contentCommitMimeTypes !== undefined) { - json.contentCommitMimeTypes.forEach((type: Any) => { - contentList.push(type); - }); - } - return new Configuration( - json.obscureText ?? false, - json.autocorrect ?? true, - json.enableSuggestions ?? false, - json.enableIMEPersonalizedLearning ?? false, - json.enableDeltaModel ?? false, - InputType.fromJson(json.inputType), - inputAction, - json.actionLabel ?? null, - json.autofill ?? null, - contentList as Any, - json.deviceKind ?? PointerDeviceKind.UNKNOWN, - fields - ); - } - - /** - * Creates a Configuration instance from a map. - * @param map - The map containing configuration data - * @returns A new Configuration instance - */ - static fromMap(map: Map) { - let inputTypeSrc: Any = map.get('inputType'); - let type = TextInputType.get(inputTypeSrc.name) ?? inputMethod.TextInputType.TEXT; - let inputType = new InputType(type, inputTypeSrc.decimal, inputTypeSrc.signed); - let inputAction = Configuration.inputActionFromTextInputAction(map.get('inputAction')); - - let fields: Array = new Array(); - if (map.get('fields')) { - fields = map.get('fields').map((field: Any): Any => Configuration.fromJson(field)); - } - - // Build list of content commit mime types from the data in the JSON list. - const contentList: Array = []; - if (map.get('contentCommitMimeTypes')) { - map.get('contentCommitMimeTypes').forEach((type: Any) => { - contentList.push(type); - }); - } - return new Configuration( - map.get('obscureText') ?? false, - map.get('autocorrect') ?? true, - map.get('enableSuggestions') ?? false, - map.get('enableIMEPersonalizedLearning') ?? false, - map.get('enableDeltaModel') ?? false, - inputType, - inputAction, - map.get('actionLabel') ?? null, - map.get('autofill') ?? null, - contentList as Any, - map.get('deviceKind') ?? PointerDeviceKind.UNKNOWN, - fields - ); - } -} - -/* -/// All possible enum values from flutter. -static const List values = [ - text, multiline, number, phone, datetime, emailAddress, url, visiblePassword, name, streetAddress, none, -]; - -// Corresponding string name for each of the [values]. -static const List _names = [ - 'text', 'multiline', 'number', 'phone', 'datetime', 'emailAddress', 'url', 'visiblePassword', 'name', 'address', 'none', -]; - -// Because TextInputType.name and TextInputType.streetAddress do not exist on ohos, -// these two types will be mapped to the default keyboard. -*/ -const TextInputType: Map = new Map([ - ["TextInputType.text", inputMethod.TextInputType.TEXT], - ["TextInputType.multiline", inputMethod.TextInputType.MULTILINE], - ["TextInputType.number", inputMethod.TextInputType.NUMBER], - ["TextInputType.phone", inputMethod.TextInputType.PHONE], - ["TextInputType.datetime", inputMethod.TextInputType.DATETIME], - ["TextInputType.emailAddress", inputMethod.TextInputType.EMAIL_ADDRESS], - ["TextInputType.url", inputMethod.TextInputType.URL], - ["TextInputType.visiblePassword", inputMethod.TextInputType.VISIBLE_PASSWORD], - ["TextInputType.name", inputMethod.TextInputType.TEXT], - ["TextInputType.address", inputMethod.TextInputType.TEXT], - ["TextInputType.none", inputMethod.TextInputType.NONE], -]); - -/** - * A text input type. - */ -export class InputType { - /** The text input type. */ - type: inputMethod.TextInputType; - /** Whether the input accepts signed numbers. */ - isSigned: boolean; - /** Whether the input accepts decimal numbers. */ - isDecimal: boolean; - - /** - * Constructs a new InputType instance. - * @param type - The text input type - * @param isSigned - Whether the input is signed - * @param isDecimal - Whether the input is decimal - */ - constructor(type: inputMethod.TextInputType, isSigned: boolean, isDecimal: boolean) { - this.type = type; - this.isSigned = isSigned; - this.isDecimal = isDecimal; - } - - /** - * Creates an InputType instance from JSON. - * @param json - The JSON object - * @returns A new InputType instance - * @throws Error if the input type is not recognized - */ - static fromJson(json: Any): InputType { - if (TextInputType.has(json.name as string)) { - return new InputType(TextInputType.get(json.name as string) as inputMethod.TextInputType, - json.signed as boolean, json.decimal as boolean) - } - throw new Error("No such TextInputType: " + json.name as string); - } -} - -/** - * State of an on-going text editing session.. - */ -export class TextEditState { - private static TAG = "TextEditState"; - /** The text content. */ - text: string; - /** The start position of the text selection. */ - selectionStart: number; - /** The end position of the text selection. */ - selectionEnd: number; - /** The start position of the composing region. */ - composingStart: number; - /** The end position of the composing region. */ - composingEnd: number; - - /** - * Constructs a new TextEditState instance. - * @param text - The text content - * @param selectionStart - The start of the selection - * @param selectionEnd - The end of the selection - * @param composingStart - The start of the composing region - * @param composingEnd - The end of the composing region - */ - constructor(text: string, - selectionStart: number, - selectionEnd: number, - composingStart: number, - composingEnd: number) { - if ((selectionStart != -1 || selectionEnd != -1) - && (selectionStart < 0 || selectionEnd < 0)) { - throw new Error("invalid selection: (" + selectionStart + ", " + selectionEnd + ")"); - } - - if ((composingStart != -1 || composingEnd != -1) - && (composingStart < 0 || composingStart > composingEnd)) { - throw new Error("invalid composing range: (" + composingStart + ", " + composingEnd + ")"); - } - - if (composingEnd > text.length) { - throw new Error("invalid composing start: " + composingStart); - } - - if (selectionStart > text.length) { - throw new Error("invalid selection start: " + selectionStart); - } - - if (selectionEnd > text.length) { - throw new Error("invalid selection end: " + selectionEnd); - } - - this.text = text; - this.selectionStart = selectionStart; - this.selectionEnd = selectionEnd; - this.composingStart = composingStart; - this.composingEnd = composingEnd; - } - - hasSelection(): boolean { - // When selectionStart == -1, it's guaranteed that selectionEnd will also - // be -1. - return this.selectionStart >= 0; - } - - /** - * Checks if there is an active composing region. - * @returns True if there is an active composing region, false otherwise - */ - hasComposing(): boolean { - return this.composingStart >= 0 && this.composingEnd > this.composingStart; - } - - /** - * Creates a TextEditState instance from JSON. - * @param textEditState - The JSON object or map - * @returns A new TextEditState instance - */ - static fromJson(textEditState: Any): TextEditState { - if (textEditState.text != null && textEditState.text != undefined && textEditState.text != "") { - return new TextEditState( - textEditState.text, - textEditState.selectionBase, - textEditState.selectionExtent, - textEditState.composingBase, - textEditState.composingExtent - ) - } else { - return new TextEditState( - textEditState.get('text'), - textEditState.get('selectionBase'), - textEditState.get('selectionExtent'), - textEditState.get('composingBase'), - textEditState.get('composingExtent') - ) - } - } -} - -/** - * Method call handler for text input channel requests. - */ -class TextInputCallback implements MethodCallHandler { - /** The text input method handler for processing text input requests. */ - textInputMethodHandler: TextInputMethodHandler; - /** The window position rectangle, or null if not set. */ - windowPosition: window.Rect | null = null; - /** The cursor position rectangle. */ - cursorPosition: window.Rect = { - left: 0, - top: 0, - width: 0, - height: 0, - } - /** The device pixel ratio for converting logical to physical pixels. */ - devicePixelRatio = display.getDefaultDisplaySync()?.densityPixels as number; - /** The input position rectangle. */ - inputPosition: window.Rect = { - left: 0, - top: 0, - width: 0, - height: 0, - } - - /** - * Constructs a new TextInputCallback instance. - * @param handler - The TextInputMethodHandler instance - */ - constructor(handler: TextInputMethodHandler) { - this.textInputMethodHandler = handler; - } - - /** - * Sets the window position. - * @param windowPosition - The window position rectangle - */ - setWindowPosition(windowPosition: window.Rect) { - this.windowPosition = windowPosition; - } - - /** - * Sets the device pixel ratio. - * @param devicePixelRatio - The device pixel ratio value - */ - setDevicePixelRatio(devicePixelRatio: number) { - this.devicePixelRatio = devicePixelRatio; - } - - /** - * Sets the cursor position based on window and input positions. - */ - setCursorPosition() { - const left = (this.windowPosition?.left ?? 0 as number) + (this.cursorPosition.left + this.inputPosition.left) * this.devicePixelRatio; - const top = (this.windowPosition?.top ?? 0 as number) + (this.cursorPosition.top + this.inputPosition.top) * this.devicePixelRatio; - this.textInputMethodHandler.setCursorSizeAndPosition({ - left: left, - top: top, - width: 100, - height: 50, - }) - } - - - /** - * Handles method calls from Dart. - * @param call - The method call from Dart - * @param result - The result callback to send a response - */ - onMethodCall(call: MethodCall, result: MethodResult) { - if (this.textInputMethodHandler == null) { - return; - } - let method: string = call.method; - let args: Any = call.args; - Log.d(TAG, "Received '" + method + "' message."); - switch (method) { - case "TextInput.show": - this.textInputMethodHandler.show(); - Log.d(TAG, "textInputMethodHandler.show()"); - result.success(null); - break; - case "TextInput.hide": - this.textInputMethodHandler.hide(); - result.success(null); - break; - case "TextInput.setClient": - const textInputClientId: number = args[0] as number; - const jsonConfiguration: string = args[1]; - const config: Configuration | null = Configuration.fromJson(jsonConfiguration); - - this.textInputMethodHandler.setClient(textInputClientId, config); - result.success(null); - break; - case 'TextInput.updateConfig': - const newConfig: Configuration | null = Configuration.fromMap(args as Map); - this.textInputMethodHandler.updateConfig(newConfig); - result.success(null); - break; - case "TextInput.requestAutofill": - //TODO: requestAutofill - result.notImplemented(); - break; - case "TextInput.setPlatformViewClient": - //TODO: - result.notImplemented(); - break; - case "TextInput.setEditingState": - this.textInputMethodHandler.setEditingState(TextEditState.fromJson(args)); - result.success(null); - break; - case "TextInput.setCaretRect": - this.cursorPosition.top = args.get('y'); - this.cursorPosition.left = args.get('x'); - this.cursorPosition.width = args.get('width'); - this.cursorPosition.height = args.get('height'); - this.setCursorPosition(); - break; - case "TextInput.setEditableSizeAndTransform": - this.inputPosition.left = args.get('transform')[12]; - this.inputPosition.top = args.get('transform')[13]; - this.setCursorPosition(); - break; - case "TextInput.clearClient": - this.textInputMethodHandler.clearClient(); - result.success(null); - break; - case "TextInput.sendAppPrivateCommand": - //TODO: - result.notImplemented(); - break; - case "TextInput.finishAutofillContext": - //TODO: - result.notImplemented(); - break; - default: - result.notImplemented(); - break; - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/workers/PlatformChannelWorker.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/workers/PlatformChannelWorker.ets deleted file mode 100644 index 7219844..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/workers/PlatformChannelWorker.ets +++ /dev/null @@ -1,72 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS'; - -import Log from '../../../util/Log'; -import SendableBinaryMessageHandler from '../../../plugin/common/SendableBinaryMessageHandler'; -import { TaskState } from '../dart/DartMessenger'; - - -const TAG: string = 'PlatformChannelWorker'; -const workerPort: ThreadWorkerGlobalScope = worker.workerPort; - -/** - * Defines the event handler to be called when the worker thread receives a message sent by the host thread. - * The event handler is executed in the worker thread. - * - * @param e - The message event data - */ -workerPort.onmessage = async (e: MessageEvents) => { - let data: TaskState = e.data; - let result: ArrayBuffer | null = await handleMessage(data.handler, data.message, data.args); - workerPort.postMessage(result, [result]); -} - -/** - * Defines the event handler to be called when the worker receives a message that cannot be deserialized. - * The event handler is executed in the worker thread. - * - * @param e - The message event data - */ -workerPort.onmessageerror = (e: MessageEvents) => { - Log.e(TAG, '#onmessageerror = ' + e.data); -} - -/** - * Defines the event handler to be called when an exception occurs during worker execution. - * The event handler is executed in the worker thread. - * - * @param e - The error event - */ -workerPort.onerror = (e: ErrorEvent) => { - Log.e(TAG, '#onerror = ' + e.message); -} - -/** - * Handles a message in the worker thread. - * @param handler - The message handler to execute - * @param message - The message data as an ArrayBuffer - * @param args - Additional arguments to pass to the handler - * @returns A promise that resolves to the reply ArrayBuffer, or null if no reply - */ -async function handleMessage(handler: SendableBinaryMessageHandler, - message: ArrayBuffer, - args: Object[]): Promise { - const result = await new Promise((resolve, reject) => { - try { - handler.onMessage(message, { - reply: (reply: ArrayBuffer | null): void => { - resolve(reply); - } - }, ...args); - } catch (e) { - reject(null); - Log.e(TAG, "Oops! Failed to handle message in the background: ", e); - } - }); - return result; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/EmbeddingNodeController.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/EmbeddingNodeController.ets deleted file mode 100644 index bc53121..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/EmbeddingNodeController.ets +++ /dev/null @@ -1,266 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -*/ -import { BuilderNode, FrameNode, NodeController, NodeRenderType } from '@kit.ArkUI'; -import Any from '../../plugin/common/Any'; -import PlatformView, { Params, PlatformViewVisibleAreaEventOptions } from '../../plugin/platform/PlatformView'; -import Log from '../../util/Log'; -import { DVModel, DVModelChildren, DynamicView } from '../../view/DynamicView/dynamicView'; - - -declare class nodeControllerParams { - surfaceId: string - type: string - renderType: NodeRenderType - embedId: string - width: number - height: number -} - -const TAG = 'EmbeddingNodeController' - -/** - * Node controller for embedding platform views in Flutter. - * This class manages the lifecycle and rendering of platform views using BuilderNode. - */ -export class EmbeddingNodeController extends NodeController { - private builderNode: BuilderNode<[Params]> | undefined | null = null; - private wrappedBuilder: WrappedBuilder<[Params]> | null = null; - private platformView: PlatformView | undefined = undefined; - private embedId: string = ""; - private surfaceId: string = ""; - private renderType: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; - private direction: Direction = Direction.Auto; - private isDestroy: boolean = false; - private platformViewVisibleAreaEventOptions: PlatformViewVisibleAreaEventOptions | null = null; - - /** - * Sets the render options for the platform view. - * @param platformView - The platform view instance - * @param surfaceId - The surface ID - * @param renderType - The render type - * @param direction - The layout direction - */ - setRenderOption(platformView: PlatformView, surfaceId: string, renderType: NodeRenderType, direction: Direction) { - if (platformView == undefined) { - Log.e(TAG, "platformView undefined"); - } else { - this.wrappedBuilder = platformView.getView(); - } - this.platformView = platformView; - this.surfaceId = surfaceId; - this.renderType = renderType; - this.direction = direction; - } - - /** - * Notify the PlatformView that it has entered an invisible state, and animations on it need to be inactive. - */ - notifyPlatformViewInvisible(): void { - if (!this.platformView || !this.platformViewVisibleAreaEventOptions || - this.platformViewVisibleAreaEventOptions.enable === false) { - return; - } - - this.platformView.onInactive(); - } - - /** - * Set up the callback for monitoring changes in the visible area of the external texture. - */ - setPlatformViewVisibleAreaEventCallback(): void { - if (!this.platformView) { - return; - } - - this.platformViewVisibleAreaEventOptions = - this.platformView.getPlatformViewVisibleAreaEventOptions(); - if (!this.platformViewVisibleAreaEventOptions) { - return; - } - - const options = this.platformViewVisibleAreaEventOptions!; - - Log.i(TAG, - "setPlatformViewVisibleAreaEventCallback surfaceId:" + this.surfaceId + - ", enable:" + options.enable + - ", ratios:" + options.ratios + - ", expectedUpdateInterval:" + options.expectedUpdateInterval + - ", onInactiveThreshold:" + options.onInactiveThreshold + - ", onActiveThreshold:" + options.onActiveThreshold); - if (options.enable === false) { - return; - } - - let node: FrameNode | null | undefined = this.builderNode?.getFrameNode(); - if (!node) { - return; - } - - node?.commonEvent.setOnVisibleAreaApproximateChange( - { ratios: options.ratios, - expectedUpdateInterval: options.expectedUpdateInterval }, - - (isExpanding: boolean, currentRatio: number) => - { - if (!this.platformView) { - return; - } - - Log.i(TAG, - "PlatformViewVisibleAreaEventCallback surfaceId:" + this.surfaceId + - ", isExpanding:" + isExpanding + - ", currentRatio:" + currentRatio); - if (!isExpanding && - currentRatio <= options.onInactiveThreshold) { - // Pause the operation of continuous production of textures - this.platformView.onInactive(); - } else if (isExpanding && - currentRatio >= options.onActiveThreshold) { - // Resume the operation of continuous production of textures - this.platformView.onActive(); - } - } - ) - } - - /** - * Creates a FrameNode for the platform view. - * @param uiContext - The UI context - * @returns The created FrameNode, or null if creation fails - */ - makeNode(uiContext: UIContext): FrameNode | null { - this.builderNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId, type: this.renderType }); - - if (this.platformView) { - this.builderNode.build(this.wrappedBuilder, { direction: this.direction, platformView: this.platformView }); - this.setPlatformViewVisibleAreaEventCallback(); - } - return this.builderNode.getFrameNode(); - } - - /** - * Sets the builder node. - * @param builderNode - The BuilderNode instance, or null - */ - setBuilderNode(builderNode: BuilderNode | null): void { - this.builderNode = builderNode; - } - - /** - * Gets the builder node. - * @returns The BuilderNode instance, or null if not set - */ - getBuilderNode(): BuilderNode<[Params]> | undefined | null { - return this.builderNode; - } - - /** - * Updates the node with new arguments. - * @param arg - The update arguments - */ - updateNode(arg: Object): void { - this.builderNode?.update(arg); - } - - /** - * Gets the embed ID. - * @returns The embed ID string - */ - getEmbedId(): string { - return this.embedId; - } - - /** - * Sets the destroy state and disposes the builder node if needed. - * @param isDestroy - Whether the controller is being destroyed - */ - setDestroy(isDestroy: boolean): void { - this.isDestroy = isDestroy; - if (this.isDestroy) { - this.builderNode?.dispose(); - } - } - - /** - * Disposes the frame node and cleans up resources. - */ - disposeFrameNode() { - this.builderNode?.getFrameNode()?.getRenderNode()?.dispose(); - this.builderNode?.dispose(); - - this.builderNode = null; - this.wrappedBuilder = null; - } - - /** - * Posts a mouse event to the builder node. - * Note: postInputEvent is an API20 interface, so we check for its existence to avoid compilation errors. - * @param event - The mouse event to post - */ - postMouseEvent(event: MouseEvent) { - // Avoid compilation errors: postInputEvent is an API20 interface - if (typeof (this.builderNode as ESObject)?.postInputEvent == 'function') { - (this.builderNode as ESObject)?.postInputEvent(event); - } - } - - /** - * Posts an axis event to the builder node. - * Note: postInputEvent is an API20 interface, so we check for its existence to avoid compilation errors. - * @param event - The axis event to post - */ - postAxisEvent(event: AxisEvent) { - // Avoid compilation errors: postInputEvent is an API20 interface - if (typeof (this.builderNode as ESObject)?.postInputEvent == 'function') { - (this.builderNode as ESObject)?.postInputEvent(event); - } - } - - /** - * Posts a touch event to the builder node. - * @param event - The touch event to post, or undefined - * @param isPx - Whether the coordinates are already in pixels (default: false, will convert from vp to px) - * @returns Whether the event was successfully posted - */ - postEvent(event: TouchEvent | undefined, isPx: boolean = false): boolean { - if (event == undefined) { - return false; - } - - // change vp to px - if (!isPx) { - let changedTouchLen = event.changedTouches.length; - for (let i = 0; i < changedTouchLen; i++) { - event.changedTouches[i].displayX = vp2px(event.changedTouches[i].displayX); - event.changedTouches[i].displayY = vp2px(event.changedTouches[i].displayY); - event.changedTouches[i].windowX = vp2px(event.changedTouches[i].windowX); - event.changedTouches[i].windowY = vp2px(event.changedTouches[i].windowY); - event.changedTouches[i].screenX = vp2px(event.changedTouches[i].screenX); - event.changedTouches[i].screenY = vp2px(event.changedTouches[i].screenY); - event.changedTouches[i].x = vp2px(event.changedTouches[i].x); - event.changedTouches[i].y = vp2px(event.changedTouches[i].y); - Log.d(TAG, "changedTouches[" + i + "] displayX:" + event.changedTouches[i].displayX + " displayY:" + - event.changedTouches[i].displayY + " x:" + event.changedTouches[i].x + " y:" + event.changedTouches[i].y); - } - let touchesLen = event.touches.length; - for (let i = 0; i< touchesLen; i++) { - event.touches[i].displayX = vp2px(event.touches[i].displayX); - event.touches[i].displayY = vp2px(event.touches[i].displayY); - event.touches[i].windowX = vp2px(event.touches[i].windowX); - event.touches[i].windowY = vp2px(event.touches[i].windowY); - event.touches[i].screenX = vp2px(event.touches[i].screenX); - event.touches[i].screenY = vp2px(event.touches[i].screenY); - event.touches[i].x = vp2px(event.touches[i].x); - event.touches[i].y = vp2px(event.touches[i].y); - Log.d(TAG, "touches[" + i + "] displayX:" + event.touches[i].displayX + " displayY:" + - event.touches[i].displayY + " x:" + event.touches[i].x + " y:" + event.touches[i].y); - } - } - - return this.builderNode?.postTouchEvent(event) as boolean - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/ExclusiveAppComponent.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/ExclusiveAppComponent.ets deleted file mode 100644 index 125d035..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/ExclusiveAppComponent.ets +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on ExclusiveAppComponent.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * Interface for app components that exclusively attach to a FlutterEngine. - * @template T - The type of the underlying app component - */ -export default interface ExclusiveAppComponent { - /** - * Called when another App Component is about to become attached to the - * {@link FlutterEngine} this App Component is currently attached to. - * - * This App Component's connections to the {@link FlutterEngine} - * are still valid at the moment of this call. - */ - detachFromFlutterEngine(): void; - - /** - * Retrieves the App Component behind this exclusive App Component. - * - * @returns The app component - */ - getAppComponent(): T; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbility.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbility.ets deleted file mode 100644 index 4992d78..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbility.ets +++ /dev/null @@ -1,546 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import UIAbility from '@ohos.app.ability.UIAbility'; -import window from '@ohos.window'; -import { FlutterAbilityAndEntryDelegate, Host } from './FlutterAbilityAndEntryDelegate'; -import Log from '../../util/Log'; -import FlutterEngine from '../engine/FlutterEngine'; -import PlatformPlugin from '../../plugin/PlatformPlugin'; -import SensitiveContentPlugin from '../../plugin/view/SensitiveContentPlugin'; -import FlutterShellArgs from '../engine/FlutterShellArgs'; -import FlutterAbilityLaunchConfigs from './FlutterAbilityLaunchConfigs'; -import common from '@ohos.app.ability.common'; -import Want from '@ohos.app.ability.Want'; -import { FlutterPlugin } from '../engine/plugins/FlutterPlugin'; -import AbilityConstant from '@ohos.app.ability.AbilityConstant'; -import I18n from '@ohos.i18n' -import { PlatformBrightness } from '../engine/systemchannels/SettingsChannel'; -import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant'; -import { Configuration } from '@ohos.app.ability.Configuration'; -import { deviceInfo } from '@kit.BasicServicesKit'; -import ExclusiveAppComponent from './ExclusiveAppComponent'; -import errorManager from '@ohos.app.ability.errorManager'; -import appRecovery from '@ohos.app.ability.appRecovery'; -import FlutterManager from './FlutterManager'; -import { FlutterView } from '../../view/FlutterView'; -import ApplicationInfoLoader from '../engine/loader/ApplicationInfoLoader'; -import { accessibility } from '@kit.AccessibilityKit'; - -const TAG = "FlutterAbility"; - -/** - * Base Flutter Ability for OpenHarmony. - * Main responsibilities: - * 1. Holds and initializes FlutterAbilityDelegate - * 2. Forwards lifecycle events - * - * Main abilities should inherit from this class. - */ -export class FlutterAbility extends UIAbility implements Host { - private delegate?: FlutterAbilityAndEntryDelegate | null; - private flutterView: FlutterView | null = null; - private mainWindow?: window.Window | null; - private errorManagerId: number = 0; - - /** - * Gets the FlutterView instance. - * @returns The FlutterView instance, or null if not available - */ - getFlutterView(): FlutterView | null { - return this.flutterView; - } - - /** - * Gets the page path for loading content. - * @returns The page path string - */ - pagePath(): string { - return "pages/Index" - } - - /** - * Determines whether FlutterAbility should be full screen by default. - * Can be overridden to customize full screen behavior. - * Default value: based on device type, determines if full screen is needed. - * @returns True if full screen by default, false otherwise - */ - isDefaultFullScreen(): boolean { - return deviceInfo.deviceType != '2in1'; - } - - /** - * Called when the ability is created. - * 1. Creates and attaches delegate - * 2. Configures windows (transparency not needed) - * 3. Handles lifecycle.onCreate - * 4. setContentView() not needed - * @param want - The Want object - * @param launchParam - The launch parameters - */ - onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { - // On cold start, get the current system font size from the context and store it - AppStorage.setOrCreate('fontSizeScale', this.context.config.fontSizeScale); - Log.i(TAG, "this.context.config.fontSizeScale = " + this.context.config.fontSizeScale); - - Log.i(TAG, "bundleCodeDir=" + this.context.bundleCodeDir); - FlutterManager.getInstance().pushUIAbility(this) - - this.delegate = new FlutterAbilityAndEntryDelegate(this); - this?.delegate?.onAttach(this.context); - Log.i(TAG, 'onAttach end'); - this?.delegate?.platformPlugin?.setUIAbilityContext(this.context); - this?.delegate?.onRestoreInstanceState(want); - - if (this.stillAttachedForEvent("onWindowStageCreate")) { - this?.delegate?.onWindowStageCreate(); - } - - Log.i(TAG, 'MyAbility onCreate'); - - let observer: errorManager.ErrorObserver = { - onUnhandledException(errorMsg) { - Log.e(TAG, "onUnhandledException, errorMsg:", errorMsg); - appRecovery.saveAppState(); - appRecovery.restartApp(); - } - } - this.errorManagerId = errorManager.on('error', observer); - - let flutterApplicationInfo = ApplicationInfoLoader.load(this.context); - - if (flutterApplicationInfo.isDebugMode) { - this.delegate?.initWindow(); - } - } - - /** - * Called when the ability is destroyed. - * Cleans up resources and removes the ability from FlutterManager. - */ - onDestroy() { - FlutterManager.getInstance().popUIAbility(this); - - errorManager.off('error', this.errorManagerId); - - if (this.flutterView != null) { - this.flutterView.onDestroy() - this.flutterView = null; - } - - if (this.stillAttachedForEvent("onDestroy")) { - this?.delegate?.onDetach(); - } - - this.release() - } - - /** - * Called to save the ability state. - * @param reason - The reason for saving state - * @param wantParam - The parameters to save state to - * @returns The save result - */ - onSaveState(reason: AbilityConstant.StateType, wantParam: Record): AbilityConstant.OnSaveResult { - return this?.delegate?.onSaveState(reason, wantParam) ?? AbilityConstant.OnSaveResult.ALL_REJECT; - } - - protected windowStageEventCallback = (data: window.WindowStageEventType) => { - this.delegate?.onWindowStageChanged(data) - } - - /** - * Called when the window stage is created. - * @param windowStage - The WindowStage instance - */ - onWindowStageCreate(windowStage: window.WindowStage) { - FlutterManager.getInstance().pushWindowStage(this, windowStage); - this.delegate?.initWindow(); - this.mainWindow = windowStage.getMainWindowSync(); - try { - windowStage.on('windowStageEvent', this.windowStageEventCallback); - this.flutterView = this.delegate!!.createView(this.context) - Log.i(TAG, 'onWindowStageCreate:' + this.flutterView!!.getId()); - let storage: LocalStorage = new LocalStorage(); - storage.setOrCreate("viewId", this.flutterView!!.getId()) - windowStage.loadContent(this.pagePath(), storage, (err, data) => { - if (err.code) { - Log.e(TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); - return; - } - this.flutterView?.onWindowCreated(); - - Log.i(TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); - }); - if (this.isDefaultFullScreen()) { - FlutterManager.getInstance().setUseFullScreen(true, this.context); - } - } catch (exception) { - Log.e(TAG, 'Failed to enable the listener for window stage event changes. Cause:' + JSON.stringify(exception)); - } - } - - /** - * Called when a new Want is received. - * @param want - The new Want object - * @param launchParams - The launch parameters - */ - onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { - this?.delegate?.onNewWant(want, launchParams) - } - - /** - * Called when the window stage is destroyed. - */ - onWindowStageDestroy() { - FlutterManager.getInstance().popWindowStage(this); - if (this.stillAttachedForEvent("onWindowStageDestroy")) { - this?.delegate?.onWindowStageDestroy(); - } - } - - /** - * Called when the ability comes to foreground. - */ - onForeground() { - if (this.stillAttachedForEvent("onForeground")) { - this?.delegate?.onShow(); - } - } - - /** - * Called when the ability goes to background. - */ - onBackground() { - if (this.stillAttachedForEvent("onBackground")) { - this?.delegate?.onHide(); - } - } - - /** - * Called when the window stage is about to be destroyed. - * @param windowStage - The WindowStage instance - */ - onWindowStageWillDestroy(windowStage: window.WindowStage) { - try { - windowStage.off('windowStageEvent', this.windowStageEventCallback); - } catch (err) { - Log.e(TAG, "windowStage off failed"); - } - } - - /** - * Releases all held objects. - */ - release() { - if (this?.delegate != null) { - this?.delegate?.release(); - this.delegate = null; - } - } - - /** - * Gets the UIAbility instance. - * @returns This UIAbility instance - */ - getAbility(): UIAbility { - return this; - } - - /** - * Gets the FlutterAbilityAndEntryDelegate instance. - * @returns The delegate instance, or null if not available - */ - getFlutterAbilityAndEntryDelegate(): FlutterAbilityAndEntryDelegate | null { - return this.delegate ?? null; - } - - /** - * Determines whether to dispatch app lifecycle state changes. - * @returns True to dispatch lifecycle state, false otherwise - */ - shouldDispatchAppLifecycleState(): boolean { - return true; - } - - /** - * Provides a FlutterEngine instance. - * @param context - The context - * @returns A FlutterEngine instance, or null if not provided - */ - provideFlutterEngine(context: common.Context): FlutterEngine | null { - return null; - } - - /** - * Provides a PlatformPlugin instance. - * @param flutterEngine - The FlutterEngine instance - * @returns A PlatformPlugin instance - */ - providePlatformPlugin(flutterEngine: FlutterEngine): PlatformPlugin | undefined { - return new PlatformPlugin(flutterEngine.getPlatformChannel()!, this.context, this); - } - - /** - * Provides a SensitiveContentPlugin instance. - * @param flutterEngine - The FlutterEngine instance - * @returns A SensitiveContentPlugin instance - */ - provideSensitiveContentPlugin(flutterEngine: FlutterEngine): SensitiveContentPlugin | undefined { - return new SensitiveContentPlugin(flutterEngine.getSensitiveContentChannel()!); - } - - /** - * Configures the Flutter engine. - * @param flutterEngine - The FlutterEngine to configure - */ - configureFlutterEngine(flutterEngine: FlutterEngine) { - - } - - /** - * Cleans up the Flutter engine. - * @param flutterEngine - The FlutterEngine to clean up - */ - cleanUpFlutterEngine(flutterEngine: FlutterEngine) { - - } - - /** - * Gets Flutter shell arguments from the Want. - * @returns A FlutterShellArgs instance - */ - getFlutterShellArgs(): FlutterShellArgs { - return FlutterShellArgs.fromWant(this.getWant()); - } - - /** - * Gets Dart entrypoint arguments from launch parameters. - * @returns Array of entrypoint arguments - */ - getDartEntrypointArgs(): Array { - if (this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS]) { - return this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS] as Array; - } - return new Array() - } - - /** - * Detaches from the Flutter engine. - */ - detachFromFlutterEngine() { - if (this?.delegate != null) { - this?.delegate?.onDetach(); - } - } - - /** - * Determines whether to pop the system navigator. - * @returns False by default - */ - popSystemNavigator(): boolean { - return false; - } - - /** - * Determines whether to attach the engine to the ability. - * @returns True to attach the engine, false otherwise - */ - shouldAttachEngineToAbility(): boolean { - return true; - } - - /** - * Gets the Dart entrypoint library URI. - * @returns The library URI string - */ - getDartEntrypointLibraryUri(): string { - return ""; - } - - /** - * Gets the app bundle path. - * @returns The bundle path string - */ - getAppBundlePath(): string { - return ""; - } - - /** - * Gets the Dart entrypoint function name from launch parameters. - * @returns The entrypoint function name, or default if not set - */ - getDartEntrypointFunctionName(): string { - if (this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT]) { - return this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT] as string; - } - return FlutterAbilityLaunchConfigs.DEFAULT_DART_ENTRYPOINT - } - - /** - * Gets the initial route from launch parameters. - * @returns The initial route string, or empty string if not set - */ - getInitialRoute(): string { - if (this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE]) { - return this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE] as string; - } - return "" - } - - /** - * Gets the Want object. - * @returns The launch Want instance - */ - getWant(): Want { - return this.launchWant; - } - - /** - * Determines whether to destroy the engine when the host is destroyed. - * @returns True to destroy the engine, false otherwise - */ - shouldDestroyEngineWithHost(): boolean { - if ((this.getCachedEngineId() != null && this.getCachedEngineId().length > 0) || - this.delegate!!.isFlutterEngineFromHost()) { - // Only destroy a cached engine if explicitly requested by app developer. - return false; - } - return true; - } - - /** - * Determines whether to automatically attach to the engine. - * @returns True to attach automatically, false otherwise - */ - attachToEngineAutomatically(): boolean { - return true; - } - - /** - * Determines whether to restore and save state. - * @returns True to restore and save state, false otherwise - */ - shouldRestoreAndSaveState(): boolean { - if (this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] != undefined) { - return this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] as boolean; - } - if (this.getCachedEngineId() != null && this.getCachedEngineId().length > 0) { - // Prevent overwriting the existing state in a cached engine with restoration state. - return false; - } - return true; - } - - /** - * Gets the exclusive app component. - * @returns The ExclusiveAppComponent instance, or null - */ - getExclusiveAppComponent(): ExclusiveAppComponent | null { - return this.delegate ? this.delegate : null - } - - /** - * Gets the cached engine ID from launch parameters. - * @returns The cached engine ID string - */ - getCachedEngineId(): string { - return this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] as string - } - - /** - * Gets the cached engine group ID from launch parameters. - * @returns The cached engine group ID string, or null if not set - */ - getCachedEngineGroupId(): string | null { - return this.launchWant.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID] as string - } - - /** - * Checks if the delegate is still attached for an event. - * @param event - The event name - * @returns True if attached, false otherwise - */ - private stillAttachedForEvent(event: string) { - Log.i(TAG, 'Ability ' + event); - if (this?.delegate == null) { - Log.w(TAG, "FlutterAbility " + event + " call after release."); - return false; - } - if (!this?.delegate?.isAttached) { - Log.w(TAG, "FlutterAbility " + event + " call after detach."); - return false; - } - return true; - } - - /** - * Adds a Flutter plugin. - * @param plugin - The FlutterPlugin to add - */ - addPlugin(plugin: FlutterPlugin): void { - if (this?.delegate != null) { - this?.delegate?.addPlugin(plugin) - } - } - - /** - * Removes a Flutter plugin. - * @param plugin - The FlutterPlugin to remove - */ - removePlugin(plugin: FlutterPlugin): void { - if (this?.delegate != null) { - this?.delegate?.removePlugin(plugin) - } - } - - /** - * Called when memory level changes. - * @param level - The memory level - */ - onMemoryLevel(level: AbilityConstant.MemoryLevel): void { - Log.i(TAG, 'onMemoryLevel: ' + level); - if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_CRITICAL) { - this?.delegate?.onLowMemory(); - } - this.delegate?.getFlutterNapi()?.SetQosOnLowMemory(level as number); - } - - /** - * Called when configuration is updated. - * @param config - The new configuration - */ - onConfigurationUpdate(config: Configuration) { - Log.i(TAG, 'onConfigurationUpdate config:' + JSON.stringify(config)); - this?.delegate?.flutterEngine?.getSettingsChannel()?.startMessage() - .setNativeSpellCheckServiceDefined(false) - .setBrieflyShowPassword(false) - .setAlwaysUse24HourFormat(I18n.System.is24HourClock()) - .setPlatformBrightness(config.colorMode != ConfigurationConstant.ColorMode.COLOR_MODE_DARK - ? PlatformBrightness.LIGHT : PlatformBrightness.DARK) - .setTextScaleFactor(config.fontSizeScale == undefined ? 1.0 : config.fontSizeScale) - .send(); //热启动生命周期内,实时监听系统设置环境改变并实时发送相应信息 - - //实时获取系统字体加粗系数 - this.delegate?.getFlutterNapi()?.setFontWeightScale(config.fontWeightScale == undefined ? 0 : - config.fontWeightScale); - Log.i(TAG, 'fontWeightScale: ' + JSON.stringify(config.fontWeightScale)); - - if (config.language != '') { - this.getFlutterEngine()?.getLocalizationPlugin()?.sendLocaleToFlutter(); - } - this?.delegate?.onCheckAndReloadFont(); - - } - - /** - * Gets the FlutterEngine instance. - * @returns The FlutterEngine instance, or null if not available - */ - getFlutterEngine(): FlutterEngine | null { - return this.delegate?.flutterEngine || null; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityAndEntryDelegate.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityAndEntryDelegate.ets deleted file mode 100644 index 2d85efc..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityAndEntryDelegate.ets +++ /dev/null @@ -1,681 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import common from '@ohos.app.ability.common'; -import FlutterEngineConfigurator from './FlutterEngineConfigurator'; -import FlutterEngineProvider from './FlutterEngineProvider'; -import FlutterEngine from '../engine/FlutterEngine'; -import PlatformPlugin, { PlatformPluginDelegate } from '../../plugin/PlatformPlugin'; -import SensitiveContentPlugin from '../../plugin/view/SensitiveContentPlugin'; -import Want from '@ohos.app.ability.Want'; -import FlutterShellArgs from '../engine/FlutterShellArgs'; -import DartExecutor, { DartEntrypoint } from '../engine/dart/DartExecutor'; -import FlutterAbilityLaunchConfigs from './FlutterAbilityLaunchConfigs'; -import Log from '../../util/Log'; -import FlutterInjector from '../../FlutterInjector'; -import UIAbility from '@ohos.app.ability.UIAbility'; -import ExclusiveAppComponent from './ExclusiveAppComponent'; -import AbilityConstant from '@ohos.app.ability.AbilityConstant'; -import { FlutterPlugin } from '../engine/plugins/FlutterPlugin'; -import FlutterEngineCache from '../engine/FlutterEngineCache'; -import FlutterEngineGroupCache from '../engine/FlutterEngineGroupCache'; -import FlutterEngineGroup, { Options } from '../engine/FlutterEngineGroup'; -import FlutterNapi from '../engine/FlutterNapi'; -import { FlutterView } from '../../view/FlutterView'; -import FlutterManager from './FlutterManager'; -import Any from '../../plugin/common/Any'; -import inputMethod from '@ohos.inputMethod'; -import window from '@ohos.window'; - -const TAG = "FlutterAbilityDelegate"; -const PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; -const FRAMEWORK_RESTORATION_BUNDLE_KEY = "framework"; - -/** - * Delegate for managing FlutterAbility and FlutterEntry lifecycle. - * Main responsibilities: - * 1. Initializes the Flutter engine - * 2. Handles ability lifecycle callbacks - */ -class FlutterAbilityAndEntryDelegate implements ExclusiveAppComponent { - protected host?: Host | null; - /** The FlutterEngine instance, or null if not created. */ - flutterEngine?: FlutterEngine | null; - /** The PlatformPlugin instance, or undefined if not set. */ - platformPlugin?: PlatformPlugin; - sensitiveContentPlugin?:SensitiveContentPlugin; - protected context?: common.Context; - protected isFlutterEngineFromHostOrCache: boolean = false; - private engineGroup?: FlutterEngineGroup; - private isHost: boolean = false; - private flutterView?: FlutterView; - private inputMethodController: inputMethod.InputMethodController = inputMethod.getController(); - private isPageShow: boolean = false; - - /** - * Constructs a new FlutterAbilityAndEntryDelegate instance. - * @param host - The Host instance, optional - */ - constructor(host?: Host) { - this.host = host; - if (this.host) { - this.isHost = true; - } - } - - /** - * Whether the delegate is still attached to the ability. - */ - isAttached = false; - - /** - * Called when the delegate is attached to a context. - * @param context - The application context - */ - onAttach(context: common.Context) { - this.context = context; - this.ensureAlive(); - if (this.flutterEngine == null) { - this.setupFlutterEngine(); - } - - if (this.host?.shouldAttachEngineToAbility()) { - // Notify any plugins that are currently attached to our FlutterEngine that they - // are now attached to an Ability. - Log.d(TAG, "Attaching FlutterEngine to the Ability that owns this delegate."); - this.flutterEngine?.getAbilityControlSurface()?.attachToAbility(this); - } - - this.platformPlugin = this.host?.providePlatformPlugin(this.flutterEngine!) - - this.sensitiveContentPlugin = this.host?.provideSensitiveContentPlugin(this.flutterEngine!) - - this.isAttached = true; - if (this.flutterEngine) { - this.flutterEngine.getSystemLanguages(); - } - if (this.flutterEngine && this.flutterView && this.host?.attachToEngineAutomatically()) { - this.flutterView.attachToFlutterEngine(this.flutterEngine!!); - } - this.host?.configureFlutterEngine(this.flutterEngine!!); - if (this.flutterEngine) { - this.flutterEngine.processPendingMessages(); - } - } - - private doInitialFlutterViewRun(): void { - let initialRoute = this.host?.getInitialRoute(); - if (initialRoute == null && this.host != null) { - initialRoute = this.maybeGetInitialRouteFromIntent(this.host.getWant()); - - } - if (initialRoute == null) { - initialRoute = FlutterAbilityLaunchConfigs.DEFAULT_INITIAL_ROUTE; - } - const libraryUri = this.host?.getDartEntrypointLibraryUri(); - Log.d(TAG, - "Executing Dart entrypoint: " + this.host?.getDartEntrypointFunctionName() + ", library uri: " + libraryUri == - null ? "\"\"" : libraryUri + ", and sending initial route: " + initialRoute); - - // The engine needs to receive the Flutter app's initial route before executing any - // Dart code to ensure that the initial route arrives in time to be applied. - this.flutterEngine?.getNavigationChannel()?.setInitialRoute(initialRoute ?? ''); - - let appBundlePathOverride = this.host?.getAppBundlePath(); - if (appBundlePathOverride == null || appBundlePathOverride == '') { - appBundlePathOverride = FlutterInjector.getInstance().getFlutterLoader().findAppBundlePath(); - } - - const dartEntrypoint: DartEntrypoint = new DartEntrypoint( - appBundlePathOverride, - this.host?.getDartEntrypointLibraryUri() ?? '', - this.host?.getDartEntrypointFunctionName() ?? '' - ); - this.flutterEngine?.dartExecutor.executeDartEntrypoint(dartEntrypoint, this.host?.getDartEntrypointArgs()); - } - - private maybeGetInitialRouteFromIntent(want: Want): string { - return ''; - } - - /** - * Configures the FlutterEngine through parameters. - * @param want - The Want object containing restoration data - */ - onRestoreInstanceState(want: Want) { - let frameworkState: Uint8Array = this.getRestorationData(want.parameters as Record); - if (this.host?.shouldRestoreAndSaveState()) { - this.flutterEngine?.getRestorationChannel()?.setRestorationData(frameworkState); - } - } - - private getRestorationData(wantParam: Record): Uint8Array { - let result: Uint8Array = new Uint8Array(1).fill(0); - if (wantParam == null) { - return result; - } - if (wantParam[FRAMEWORK_RESTORATION_BUNDLE_KEY] == undefined) { - return result - } - if (typeof wantParam[FRAMEWORK_RESTORATION_BUNDLE_KEY] == 'object') { - let data: Record = wantParam[FRAMEWORK_RESTORATION_BUNDLE_KEY] as Record; - let byteArray: Array = new Array; - Object.keys(data).forEach( - key => { - byteArray.push(data[key]); - } - ); - result = Uint8Array.from(byteArray); - } - return result; - } - - /** - * Initializes the FlutterEngine. - * Checks for cached engines, engine groups, or creates a new engine. - */ - setupFlutterEngine() { - // First, check if the host wants to use a cached FlutterEngine. - const cachedEngineId = this.host?.getCachedEngineId(); - Log.d(TAG, "cachedEngineId=" + cachedEngineId); - if (cachedEngineId && cachedEngineId.length > 0) { - this.flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId); - this.isFlutterEngineFromHostOrCache = true; - if (this.flutterEngine == null) { - throw new Error( - "The requested cached FlutterEngine did not exist in the FlutterEngineCache: '" - + cachedEngineId - + "'"); - } - return; - } - - // Second, defer to subclasses for a custom FlutterEngine. - if (this.host && this.context) { - this.flutterEngine = this.host.provideFlutterEngine(this.context); - } - if (this.flutterEngine != null) { - this.isFlutterEngineFromHostOrCache = true; - return; - } - - // Third, check if the host wants to use a cached FlutterEngineGroup - // and create new FlutterEngine using FlutterEngineGroup#createAndRunEngine - const cachedEngineGroupId = this.host?.getCachedEngineGroupId(); - Log.d(TAG, "cachedEngineGroupId=" + cachedEngineGroupId); - if (cachedEngineGroupId != null) { - const flutterEngineGroup = FlutterEngineGroupCache.instance.get(cachedEngineGroupId); - if (flutterEngineGroup == null) { - throw new Error( - "The requested cached FlutterEngineGroup did not exist in the FlutterEngineGroupCache: '" - + cachedEngineGroupId - + "'"); - } - - if (this.context != null) { - this.flutterEngine = flutterEngineGroup.createAndRunEngineByOptions( - this.addEntrypointOptions(new Options(this.context))); - } - this.isFlutterEngineFromHostOrCache = false; - return; - } - - // Our host did not provide a custom FlutterEngine. Create a FlutterEngine to back our - // FlutterView. - Log.d( - TAG, - "No preferred FlutterEngine was provided. Creating a new FlutterEngine for this FlutterAbility."); - - let group = this.engineGroup; - if (group == null && this.context != null) { - group = new FlutterEngineGroup(); - const flutterShellArgs = this.host ? this.host.getFlutterShellArgs() : new FlutterShellArgs(); - group.checkLoader(this.context, flutterShellArgs.toArray() ?? []); - this.engineGroup = group; - } - if (this.context) { - this.flutterEngine = group?.createAndRunEngineByOptions(this.addEntrypointOptions(new Options(this.context) - .setWaitForRestorationData(this.host?.shouldRestoreAndSaveState() || false))); - } - this.isFlutterEngineFromHostOrCache = false; - } - - /** - * Adds entrypoint options to the engine options. - * @param options - The Options instance to configure - * @returns The configured Options instance - */ - addEntrypointOptions(options: Options): Options { - let appBundlePathOverride = this.host?.getAppBundlePath(); - if (appBundlePathOverride == null || appBundlePathOverride.length == 0) { - appBundlePathOverride = FlutterInjector.getInstance().getFlutterLoader().findAppBundlePath(); - } - - const dartEntrypoint = new DartEntrypoint(appBundlePathOverride ?? '', - '', - this.host?.getDartEntrypointFunctionName() ?? ''); - let initialRoute = this.host?.getInitialRoute(); - if (initialRoute == null && this.host != null) { - initialRoute = this.maybeGetInitialRouteFromIntent(this.host.getWant()); - } - if (initialRoute == null) { - initialRoute = FlutterAbilityLaunchConfigs.DEFAULT_INITIAL_ROUTE; - } - return options - .setDartEntrypoint(dartEntrypoint) - .setInitialRoute(initialRoute) - .setDartEntrypointArgs(this.host?.getDartEntrypointArgs() ?? []); - } - - /** - * Creates a FlutterView instance. - * @param context - The context for creating the view - * @returns A new FlutterView instance - */ - createView(context: Context): FlutterView { - this.flutterView = FlutterManager.getInstance().createFlutterView(context) - if (this.flutterEngine && this.host?.attachToEngineAutomatically()) { - this.flutterView.attachToFlutterEngine(this.flutterEngine!!); - } - return this.flutterView - } - - /** - * Releases all held objects. - */ - release() { - this.host = null; - this.flutterEngine = null; - this.platformPlugin = undefined; - } - - /** - * Called when the delegate is detached from the ability. - * Cleans up resources and optionally destroys the engine. - */ - onDetach() { - if (this.host?.shouldAttachEngineToAbility()) { - // Notify plugins that they are no longer attached to an Ability. - Log.d(TAG, "Detaching FlutterEngine from the Ability"); - this.flutterEngine?.getAbilityControlSurface()?.detachFromAbility(); - } - this.flutterView?.detachFromFlutterEngine(); - this.host?.cleanUpFlutterEngine(this.flutterEngine!!); - - if (this.host?.shouldDispatchAppLifecycleState() && this.flutterEngine != null) { - this.flutterEngine?.getLifecycleChannel()?.appIsDetached(); - } - - if (this.platformPlugin) { - this.platformPlugin.destroy(); - } - - if (this.sensitiveContentPlugin) { - this.sensitiveContentPlugin.destroy(); - } - - // Destroy our FlutterEngine if we're not set to retain it. - if (this.host?.shouldDestroyEngineWithHost()) { - this.flutterEngine?.destroy(); - if (this.host.getCachedEngineId() != null && this.host.getCachedEngineId().length > 0) { - FlutterEngineCache.getInstance().remove(this.host.getCachedEngineId()); - } - this.flutterEngine = null; - } - - this.isAttached = false; - } - - /** - * Called when the system is running low on memory. - * Notifies Flutter about the memory pressure. - */ - onLowMemory(): void { - this.getFlutterNapi()?.notifyLowMemoryWarning(); - this.flutterEngine?.getSystemChannel()?.sendMemoryPressureWarning(); - } - - /** - * Called when the window stage is created. - * Runs the initial Flutter view. - */ - onWindowStageCreate() { - this.ensureAlive(); - this.doInitialFlutterViewRun(); - } - - /** - * Called when the window stage is destroyed. - */ - onWindowStageDestroy() { - - } - - /** - * Called when the window stage state changes. - * @param stageEventType - The window stage event type - */ - onWindowStageChanged(stageEventType: window.WindowStageEventType) { - switch (stageEventType) { - case window.WindowStageEventType.SHOWN: - Log.i(TAG, 'windowStage shown.'); - break; - case window.WindowStageEventType.ACTIVE: // 获焦状态 - Log.i(TAG, 'windowStage active.'); - this.getFlutterEngine()?.getTextInputChannel()?.textInputMethodHandler?.handleChangeFocus(true); - this.onWindowFocusChanged(true); - break; - case window.WindowStageEventType.INACTIVE: // 失焦状态 - Log.i(TAG, 'windowStage inactive.'); - this.onWindowFocusChanged(false); - break; - case window.WindowStageEventType.PAUSED: - Log.i(TAG, 'windowStage paused.'); - this.onPaused(); - break; - case window.WindowStageEventType.RESUMED: - Log.i(TAG, 'windowStage resumed.'); - this.onResumed(); - break; - case window.WindowStageEventType.HIDDEN: - Log.i(TAG, 'windowStage hidden.'); - break; - } - } - - /** - * Called when window focus changes. - * @param hasFocus - Whether the window has focus - */ - onWindowFocusChanged(hasFocus: boolean): void { - if (this.shouldDispatchAppLifecycleState()) { - this.flutterEngine?.getAbilityControlSurface()?.onWindowFocusChanged(hasFocus); - if (hasFocus) { - this.flutterEngine?.getLifecycleChannel()?.aWindowIsFocused(); - } else { - this.flutterEngine?.getLifecycleChannel()?.noWindowsAreFocused(); - } - } - } - - /** - * Called when the ability is shown. - * Notifies Flutter that the app is resumed. - */ - onShow() { - this.ensureAlive(); - this.isPageShow = true; - this.flutterView?.setActive(true); - if (this.shouldDispatchAppLifecycleState()) { - this.flutterEngine?.getLifecycleChannel()?.appIsResumed(); - } - } - - /** - * Called when the ability is paused. - * Notifies Flutter that the app is inactive. - */ - onPaused() { - if (this.shouldDispatchAppLifecycleState()) { - this.flutterEngine?.getLifecycleChannel()?.appIsInactive(); - } - } - - /** - * Called when the ability is resumed. - * Notifies Flutter that the app is resumed. - */ - onResumed() { - if (this.shouldDispatchAppLifecycleState()) { - this.flutterEngine?.getLifecycleChannel()?.appIsResumed(); - } - } - - /** - * Called when the ability is hidden. - * Notifies Flutter that the app is paused. - */ - onHide() { - if (this.shouldDispatchAppLifecycleState()) { - this.isPageShow = false; - this.flutterView?.setActive(false); - this.flutterEngine?.getLifecycleChannel()?.appIsPaused(); - } - } - - /** - * Checks and reloads fonts if needed. - */ - onCheckAndReloadFont() { - this.getFlutterNapi()?.checkAndReloadFont(); - } - - /** - * Determines whether to dispatch app lifecycle state changes. - * @returns True to dispatch lifecycle state, false otherwise - */ - shouldDispatchAppLifecycleState(): boolean { - if (!this.isHost) { - return this.isAttached; - } - if (this.host == null) { - return false; - } - if (!this.isPageShow) { - return false; - } - return this.host.shouldDispatchAppLifecycleState() && this.isAttached; - } - - /** - * Ensures the delegate is still alive. - * @throws Error if the delegate has been destroyed - */ - ensureAlive() { - if (this.isHost && this.host == null) { - throw new Error("Cannot execute method on a destroyed FlutterAbilityDelegate."); - } - } - - /** - * Gets the FlutterNapi instance. - * @returns The FlutterNapi instance, or null if not available - */ - getFlutterNapi(): FlutterNapi | null { - return this.flutterEngine?.getFlutterNapi() ?? null - } - - /** - * Gets the FlutterEngine instance. - * @returns The FlutterEngine instance, or null if not available - */ - getFlutterEngine(): FlutterEngine | null { - return this.flutterEngine ?? null; - } - - /** - * Detaches from the Flutter engine. - * @throws Error if the engine should not be detached - */ - detachFromFlutterEngine() { - if (this.host?.shouldDestroyEngineWithHost()) { - // The host owns the engine and should never have its engine taken by another exclusive - // ability. - throw new Error( - "The internal FlutterEngine created by " - + this.host - + " has been attached to by another Ability. To persist a FlutterEngine beyond the " - + "ownership of this ability, explicitly create a FlutterEngine"); - } - - // Default, but customizable, behavior is for the host to call {@link #onDetach} - // deterministically as to not mix more events during the lifecycle of the next exclusive - // ability. - this.host?.detachFromFlutterEngine(); - } - - /** - * Gets the app component (UIAbility). - * @returns The UIAbility instance - * @throws Error if the ability is null - */ - getAppComponent(): UIAbility { - const ability = this.host?.getAbility(); - if (ability == null) { - throw new Error( - "FlutterAbilityAndFragmentDelegate's getAppComponent should only " - + "be queried after onAttach, when the host's ability should always be non-null"); - } - return ability; - } - - /** - * Called when a new Want is received. - * @param want - The new Want object - * @param launchParams - The launch parameters - */ - onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { - this.ensureAlive() - if (this.flutterEngine != null) { - Log.i(TAG, "Forwarding onNewWant() to FlutterEngine and sending pushRouteInformation message."); - this.flutterEngine?.getAbilityControlSurface()?.onNewWant(want, launchParams); - const initialRoute = this.maybeGetInitialRouteFromIntent(want); - if (initialRoute && initialRoute.length > 0) { - this.flutterEngine?.getNavigationChannel()?.pushRouteInformation(initialRoute); - } - } else { - Log.w(TAG, "onNewIntent() invoked before FlutterFragment was attached to an Ability."); - } - } - - /** - * Called to save the ability state. - * @param reason - The reason for saving state - * @param wantParam - The parameters to save state to - * @returns The save result - */ - onSaveState(reason: AbilityConstant.StateType, wantParam: Record): AbilityConstant.OnSaveResult { - Log.i(TAG, "onSaveInstanceState. Giving framework and plugins an opportunity to save state."); - this.ensureAlive(); - if (this.host?.shouldRestoreAndSaveState()) { - wantParam[FRAMEWORK_RESTORATION_BUNDLE_KEY] = this.flutterEngine!.getRestorationChannel()!.getRestorationData(); - } - if (this.host?.shouldAttachEngineToAbility()) { - const plugins: Record = {} - const result = this.flutterEngine?.getAbilityControlSurface()?.onSaveState(reason, plugins); - wantParam[PLUGINS_RESTORATION_BUNDLE_KEY] = plugins; - return result ?? AbilityConstant.OnSaveResult.ALL_REJECT - } - return AbilityConstant.OnSaveResult.ALL_REJECT - } - - /** - * Adds a Flutter plugin. - * @param plugin - The FlutterPlugin to add - */ - addPlugin(plugin: FlutterPlugin): void { - this.flutterEngine?.getPlugins()?.add(plugin) - } - - /** - * Removes a Flutter plugin. - * @param plugin - The FlutterPlugin to remove - */ - removePlugin(plugin: FlutterPlugin): void { - this.flutterEngine?.getPlugins()?.remove(plugin.getUniqueClassName()) - } - - /** - * Checks if the FlutterEngine was provided by the host or cache. - * @returns True if from host or cache, false if created by delegate - */ - isFlutterEngineFromHost(): boolean { - return this.isFlutterEngineFromHostOrCache; - } - - /** - * Initializes the window. - */ - initWindow() { - if (this.flutterEngine && this.isAttached) { - this.platformPlugin?.initWindow() - } - } -} - -/** - * Interface for FlutterAbility host. - * This interface extends FlutterEngineProvider, FlutterEngineConfigurator, and PlatformPluginDelegate. - */ -interface Host extends FlutterEngineProvider, FlutterEngineConfigurator, PlatformPluginDelegate { - - getAbility(): UIAbility; - - shouldDispatchAppLifecycleState(): boolean; - - detachFromFlutterEngine(): void; - - shouldAttachEngineToAbility(): boolean; - - getCachedEngineId(): string; - - getCachedEngineGroupId(): string | null; - - /** - * Returns true if the {@link io.flutter.embedding.engine.FlutterEngine} used in this delegate - * should be destroyed when the host/delegate are destroyed. - */ - shouldDestroyEngineWithHost(): boolean; - - /** Returns the {@link FlutterShellArgs} that should be used when initializing Flutter. */ - getFlutterShellArgs(): FlutterShellArgs; - - /** Returns arguments that passed as a list of string to Dart's entrypoint function. */ - getDartEntrypointArgs(): Array; - - /** - * Returns the URI of the Dart library which contains the entrypoint method (example - * "package:foo_package/main.dart"). If null, this will default to the same library as the - * `main()` function in the Dart program. - */ - getDartEntrypointLibraryUri(): string; - - /** Returns the path to the app bundle where the Dart code exists. */ - getAppBundlePath(): string; - - /** - * Returns the Dart entrypoint that should run when a new {@link io.flutter.embedding.engine.FlutterEngine} is created. - */ - getDartEntrypointFunctionName(): string; - - /** Returns the initial route that Flutter renders. */ - getInitialRoute(): string; - - getWant(): Want; - - shouldRestoreAndSaveState(): boolean; - - getExclusiveAppComponent(): ExclusiveAppComponent | null - - providePlatformPlugin(flutterEngine: FlutterEngine): PlatformPlugin | undefined - - provideSensitiveContentPlugin(flutterEngine: FlutterEngine): SensitiveContentPlugin | undefined - - /** - * Whether to automatically attach the {@link FlutterView} to the engine. - * - * In the add-to-app scenario where multiple {@link FlutterView} share the same {@link FlutterEngine}, - * the host application desires to determine the timing of attaching the {@link FlutterView} - * to the engine, for example, during the {@code onResume} instead of the {@code onCreateView}. - - * - * Defaults to {@code true}. - */ - attachToEngineAutomatically(): boolean; -} - -export { Host, FlutterAbilityAndEntryDelegate } \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ets deleted file mode 100644 index d413496..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ets +++ /dev/null @@ -1,44 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -/** - * The mode of the background of a Flutter Ability, either opaque or transparent. - */ -enum BackgroundMode { - /** Indicates a Flutter Ability with an opaque background. This is the default. */ - opaque, - /** Indicates a Flutter Ability with a transparent background. */ - transparent -} - -/** - * Configuration constants for Flutter Ability launch parameters. - * This class contains metadata keys, Want extras, and default values for launching Flutter abilities. - */ -export default class FlutterAbilityLaunchConfigs { - static DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint"; - static DART_ENTRYPOINT_URI_META_DATA_KEY = "io.flutter.EntrypointUri"; - static INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute"; - static SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable"; - static NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme"; - static HANDLE_DEEPLINKING_META_DATA_KEY = "flutter_deeplinking_enabled"; - // Want extra arguments. - static EXTRA_DART_ENTRYPOINT = "dart_entrypoint"; - static EXTRA_DART_ENTRYPOINT_LIBRARY_URI = "dart_entrypoint_library_uri"; - static EXTRA_INITIAL_ROUTE = "route"; - static EXTRA_BACKGROUND_MODE = "background_mode"; - static EXTRA_CACHED_ENGINE_ID = "cached_engine_id"; - static EXTRA_DART_ENTRYPOINT_ARGS = "dart_entrypoint_args"; - static EXTRA_CACHED_ENGINE_GROUP_ID = "cached_engine_group_id"; - static EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity"; - static EXTRA_ENABLE_STATE_RESTORATION = "enable_state_restoration"; - // Default configuration. - static DEFAULT_DART_ENTRYPOINT = "main"; - static DEFAULT_INITIAL_ROUTE = "/"; - static DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque; - // Preload configuration. - static PRELOAD_VIEWPORT_METRICS_KEY = "viewport_metrics"; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ets deleted file mode 100644 index 3db0dda..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ets +++ /dev/null @@ -1,29 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterEngineConfigurator.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import FlutterEngine from '../engine/FlutterEngine'; - -/** - * Interface for configuring FlutterEngine instances. - * Implementations can customize engine setup and cleanup. - */ -export default interface FlutterEngineConfigurator { - /** - * Configures a FlutterEngine instance. - * @param flutterEngine - The FlutterEngine to configure - */ - configureFlutterEngine: (flutterEngine: FlutterEngine) => void; - - /** - * Cleans up a FlutterEngine instance. - * @param flutterEngine - The FlutterEngine to clean up - */ - cleanUpFlutterEngine: (flutterEngine: FlutterEngine) => void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEngineProvider.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEngineProvider.ets deleted file mode 100644 index 59a6bc9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEngineProvider.ets +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterEngineProvider.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import FlutterEngine from '../engine/FlutterEngine'; -import common from '@ohos.app.ability.common'; - -/** - * Interface for providing FlutterEngine instances. - * Implementations of this interface can provide custom FlutterEngine instances. - */ -export default interface FlutterEngineProvider { - /** - * Provides a FlutterEngine instance for the given context. - * @param context - The application context - * @returns A FlutterEngine instance, or null if no engine should be provided - */ - provideFlutterEngine(context: common.Context): FlutterEngine | null; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEntry.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEntry.ets deleted file mode 100644 index f4146d9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterEntry.ets +++ /dev/null @@ -1,468 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import FlutterEngine from '../engine/FlutterEngine'; -import PlatformPlugin from '../../plugin/PlatformPlugin'; -import Want from '@ohos.app.ability.Want'; -import FlutterShellArgs from '../engine/FlutterShellArgs'; -import UIAbility from '@ohos.app.ability.UIAbility'; -import ExclusiveAppComponent from './ExclusiveAppComponent'; -import { FlutterAbilityAndEntryDelegate, Host } from './FlutterAbilityAndEntryDelegate'; -import FlutterAbilityLaunchConfigs from './FlutterAbilityLaunchConfigs'; -import SensitiveContentPlugin from '../../plugin/view/SensitiveContentPlugin'; -import Log from '../../util/Log'; -import { FlutterView } from '../../view/FlutterView'; -import FlutterManager from './FlutterManager'; -import window from '@ohos.window'; -import FlutterEngineConfigurator from './FlutterEngineConfigurator'; -import { FlutterPlugin } from '../engine/plugins/FlutterPlugin'; -import { BusinessError } from '@ohos.base'; -import { PlatformBrightness } from '../engine/systemchannels/SettingsChannel'; -import ConfigurationConstant from '@ohos.app.ability.ConfigurationConstant'; -import I18n from '@ohos.i18n' -import { AbilityConstant, EnvironmentCallback } from '@kit.AbilityKit'; - -const TAG = "FlutterEntry"; - -/** - * Entry point for Flutter content in a page component. - * This class manages the lifecycle of FlutterView and FlutterEngine within a page context. - */ -export default class FlutterEntry implements Host { - private static ARG_SHOULD_ATTACH_ENGINE_TO_ABILITY: string = "should_attach_engine_to_ability"; - protected uiAbility: UIAbility | null = null - protected delegate: FlutterAbilityAndEntryDelegate | null = null - protected flutterView: FlutterView | null = null - protected context: Context; - protected windowStage: window.WindowStage | null = null - private parameters: Record = {}; - protected engineConfigurator: FlutterEngineConfigurator | null = null - protected hasInit: boolean = false; - protected callbackId: number | undefined = undefined; - - /** - * Constructs a new FlutterEntry instance. - * @param context - The page context - * @param params - Optional parameters for configuration - */ - constructor(context: Context, params: Record = {}) { - this.context = context; - this.uiAbility = FlutterManager.getInstance().getUIAbility(context); - this.parameters = params; - this.windowStage = FlutterManager.getInstance().getWindowStage(this.uiAbility); - this.hasInit = false; - } - - /** - * Callback for window stage events. - * @param data - The window stage event type - */ - protected windowStageEventCallback = (data: window.WindowStageEventType) => { - this.delegate?.onWindowStageChanged(data) - } - - /** - * Called when the page is about to appear. - * Initializes the Flutter delegate and view. - */ - aboutToAppear() { - Log.i(TAG, 'aboutToAppear'); - if (this.hasInit == false) { - this.delegate = new FlutterAbilityAndEntryDelegate(this); - this.flutterView = this.delegate?.createView(this.context); - this.flutterView?.onWindowCreated(); - this?.delegate?.onAttach(this.context); - //this.flutterView?.preDraw(); - //Log.d(TAG, "XComponent aboutToAppear predraw"); - Log.i(TAG, 'onAttach end'); - this?.delegate?.platformPlugin?.setUIAbilityContext(this.uiAbility!!.context); - this.delegate?.onWindowStageCreate() - this.windowStage?.on('windowStageEvent', this.windowStageEventCallback); - this.hasInit = true; - this.delegate?.initWindow(); - this.registerEnvironmentCallback(); - } - } - - /** - * Registers an environment callback to listen for configuration changes. - */ - registerEnvironmentCallback() { - let environmentCallback: EnvironmentCallback = { - onConfigurationUpdated: (config) => { - Log.i(TAG, 'onConfigurationUpdate config: ' + JSON.stringify(config)); - this?.delegate?.flutterEngine?.getSettingsChannel()?.startMessage() - .setNativeSpellCheckServiceDefined(false) - .setBrieflyShowPassword(false) - .setAlwaysUse24HourFormat(I18n.System.is24HourClock()) - .setPlatformBrightness(config.colorMode != ConfigurationConstant.ColorMode.COLOR_MODE_DARK - ? PlatformBrightness.LIGHT : PlatformBrightness.DARK) - .setTextScaleFactor(config.fontSizeScale == undefined ? 1.0 : config.fontSizeScale) - .send(); //热启动生命周期内,实时监听系统设置环境改变并实时发送相应信息 - - //实时获取系统字体加粗系数 - this.delegate?.getFlutterNapi()?.setFontWeightScale(config.fontWeightScale == undefined ? 0 : - config.fontWeightScale); - Log.i(TAG, 'fontWeightScale: ' + JSON.stringify(config.fontWeightScale)); - - if (config.language != '') { - this.delegate?.flutterEngine?.getLocalizationPlugin()?.sendLocaleToFlutter(); - } - this?.delegate?.onCheckAndReloadFont(); - - }, - onMemoryLevel: (level: AbilityConstant.MemoryLevel) => { - this.delegate?.getFlutterNapi()?.SetQosOnLowMemory(level as number); - } - }; - let applicationContext = this.uiAbility?.context.getApplicationContext(); - try { - this.callbackId = applicationContext?.on('environment', environmentCallback); - } catch (paramError) { - Log.e(TAG, 'registerEnvironmentCallback error: ' + (paramError as BusinessError).code + ' message: ' - + (paramError as BusinessError).message); - } - } - - /** - * Unregisters the environment callback. - */ - unregisterEnvironmentCallback() { - let applicationContext = this.uiAbility?.context.getApplicationContext(); - try { - applicationContext?.off('environment', this.callbackId, (error, data) => { - if (error && error.code !== 0) { - Log.e(TAG, 'unregisterEnvironmentCallback fail, error: ' + JSON.stringify(error)); - } - }); - } catch (paramError) { - Log.e(TAG, 'error: ' + (paramError as BusinessError).code + ' message: ' - + (paramError as BusinessError).message); - } - } - - /** - * Sets the Flutter engine configurator. - * @param configurator - The FlutterEngineConfigurator instance - */ - setFlutterEngineConfigurator(configurator: FlutterEngineConfigurator) { - this.engineConfigurator = configurator; - } - - /** - * Gets the FlutterView instance. - * @returns The FlutterView instance - */ - getFlutterView(): FlutterView { - return this.flutterView!! - } - - /** - * Gets the FlutterEngine instance. - * @returns The FlutterEngine instance, or null if not available - */ - getFlutterEngine(): FlutterEngine | null { - return this.delegate?.flutterEngine! - } - - /** - * Called when the page is about to disappear. - * Cleans up resources and detaches from the Flutter engine. - */ - aboutToDisappear() { - Log.d(TAG, "FlutterEntry aboutToDisappear"); - this.unregisterEnvironmentCallback(); - try { - this.windowStage?.off('windowStageEvent', this.windowStageEventCallback); - } catch (err) { - Log.e(TAG, "windowStage off failed"); - } - if (this.flutterView != null) { - this.flutterView.onDestroy(); - this.flutterView = null; - } - if (this.delegate != null) { - this.delegate?.onDetach(); - this.delegate?.release() - } - } - - /** - * Called when the page is shown. - * Notifies the delegate that the page is visible. - */ - onPageShow() { //生命周期 - Log.d(TAG, "FlutterEntry onPageShow"); - this?.delegate?.onShow(); - } - - /** - * Called when the page is hidden. - * Notifies the delegate that the page is hidden. - */ - onPageHide() { //生命周期 - Log.d(TAG, "FlutterEntry onPageHide"); - this?.delegate?.onHide(); - } - - /** - * Called when the back button is pressed. - * Pops the current route in Flutter navigation. - */ - onBackPress() { - Log.d(TAG, "FlutterEntry onBackPress"); - this?.delegate?.flutterEngine?.getNavigationChannel()?.popRoute(); - } - - /** - * Determines whether to dispatch app lifecycle state changes. - * @returns True to dispatch lifecycle state, false otherwise - */ - shouldDispatchAppLifecycleState(): boolean { - return true; - } - - /** - * Detaches from the Flutter engine. - */ - detachFromFlutterEngine() { - if (this?.delegate != null) { - this?.delegate?.onDetach(); - } - } - - /** - * Gets the associated UIAbility. - * @returns The UIAbility instance - */ - getAbility(): UIAbility { - return this.uiAbility!! - } - - /** - * Loads the page content. - * This method is called by the framework. - */ - loadContent() { - - } - - /** - * Determines whether to attach the engine to the ability. - * @returns True to attach the engine, false otherwise - */ - shouldAttachEngineToAbility(): boolean { - let param = this.parameters![FlutterEntry.ARG_SHOULD_ATTACH_ENGINE_TO_ABILITY]; - if (!param) { - return true; - } - return param as boolean - } - - /** - * Gets the cached engine ID from parameters. - * @returns The cached engine ID, or empty string if not set - */ - getCachedEngineId(): string { - let param = this.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID]; - if (!param) { - return ""; - } - return param as string - } - - /** - * Gets the cached engine group ID from parameters. - * @returns The cached engine group ID, or null if not set - */ - getCachedEngineGroupId(): string | null { - let param = this.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID]; - if (!param) { - return null; - } - return param as string - } - - /** - * Determines whether to destroy the engine when the host is destroyed. - * @returns True to destroy the engine, false otherwise - */ - shouldDestroyEngineWithHost(): boolean { - if ((this.getCachedEngineId() != null && this.getCachedEngineId().length > 0) || - this.delegate!!.isFlutterEngineFromHost()) { - // Only destroy a cached engine if explicitly requested by app developer. - return false; - } - return true; - } - - /** - * Determines whether to automatically attach to the engine. - * @returns True to attach automatically, false otherwise - */ - attachToEngineAutomatically(): boolean { - return true; - } - - /** - * Gets Flutter shell arguments. - * @returns A FlutterShellArgs instance - */ - getFlutterShellArgs(): FlutterShellArgs { - return new FlutterShellArgs(); - } - - /** - * Gets Dart entrypoint arguments from parameters. - * @returns Array of entrypoint arguments - */ - getDartEntrypointArgs(): string[] { - if (this.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS]) { - return this.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT_ARGS] as Array; - } - return new Array() - } - - /** - * Gets the Dart entrypoint library URI. - * @returns The library URI string - */ - getDartEntrypointLibraryUri(): string { - return ""; - } - - /** - * Gets the app bundle path. - * @returns The bundle path string - */ - getAppBundlePath(): string { - return ""; - } - - /** - * Gets the Dart entrypoint function name from parameters. - * @returns The entrypoint function name, or default if not set - */ - getDartEntrypointFunctionName(): string { - if (this.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT]) { - return this.parameters![FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT] as string; - } - return FlutterAbilityLaunchConfigs.DEFAULT_DART_ENTRYPOINT - } - - /** - * Gets the initial route from parameters. - * @returns The initial route string, or empty string if not set - */ - getInitialRoute(): string { - if (this.parameters![FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE]) { - return this.parameters![FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE] as string - } - return ""; - } - - /** - * Gets the Want object. - * @returns A new Want instance - */ - getWant(): Want { - return new Want(); - } - - /** - * Determines whether to restore and save state. - * @returns True to restore and save state, false otherwise - */ - shouldRestoreAndSaveState(): boolean { - if (this.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] != undefined) { - return this.parameters![FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] as boolean; - } - if (this.getCachedEngineId() != null && this.getCachedEngineId().length > 0) { - // Prevent overwriting the existing state in a cached engine with restoration state. - return false; - } - return true; - } - - /** - * Gets the exclusive app component. - * @returns The ExclusiveAppComponent instance, or null - */ - getExclusiveAppComponent(): ExclusiveAppComponent | null { - return this.delegate ? this.delegate : null - } - - /** - * Provides a FlutterEngine instance. - * @param context - The context - * @returns A FlutterEngine instance, or null if not provided - */ - provideFlutterEngine(context: Context): FlutterEngine | null { - return null; - } - - /** - * Provides a PlatformPlugin instance. - * @param flutterEngine - The FlutterEngine instance - * @returns A PlatformPlugin instance - */ - providePlatformPlugin(flutterEngine: FlutterEngine): PlatformPlugin | undefined { - return new PlatformPlugin(flutterEngine.getPlatformChannel()!, this.context, this); - } - - /** - * Provides a SensitiveContentPlugin instance. - * @param flutterEngine - The FlutterEngine instance - * @returns A SensitiveContentPlugin instance - */ - provideSensitiveContentPlugin(flutterEngine: FlutterEngine): SensitiveContentPlugin | undefined { - return new SensitiveContentPlugin(flutterEngine.getSensitiveContentChannel()!); - } - - /** - * Configures the Flutter engine. - * @param flutterEngine - The FlutterEngine to configure - */ - configureFlutterEngine(flutterEngine: FlutterEngine) { - if (this.engineConfigurator) { - this.engineConfigurator.configureFlutterEngine(flutterEngine) - } - } - - /** - * Cleans up the Flutter engine. - * @param flutterEngine - The FlutterEngine to clean up - */ - cleanUpFlutterEngine(flutterEngine: FlutterEngine) { - if (this.engineConfigurator) { - this.engineConfigurator.cleanUpFlutterEngine(flutterEngine) - } - } - - /** - * Determines whether to pop the system navigator. - * @returns False by default - */ - popSystemNavigator(): boolean { - return false; - } - - /** - * Adds a Flutter plugin. - * @param plugin - The FlutterPlugin to add - */ - addPlugin(plugin: FlutterPlugin): void { - this.delegate?.addPlugin(plugin) - } - - /** - * Removes a Flutter plugin. - * @param plugin - The FlutterPlugin to remove - */ - removePlugin(plugin: FlutterPlugin): void { - this.delegate?.removePlugin(plugin) - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterManager.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterManager.ets deleted file mode 100644 index ecedd45..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterManager.ets +++ /dev/null @@ -1,463 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - - -import { FlutterView } from '../../view/FlutterView'; -import UIAbility from '@ohos.app.ability.UIAbility'; -import window from '@ohos.window'; -import Log from '../../util/Log'; -import HashMap from '@ohos.util.HashMap'; -import List from '@ohos.util.List'; -import { deviceInfo } from '@kit.BasicServicesKit'; -import { common } from '@kit.AbilityKit'; - -const TAG = "FlutterManager" - -/** - * Singleton manager for Flutter views and UI abilities. - * This class manages the lifecycle of FlutterView instances and their association with UI abilities. - */ -export default class FlutterManager { - private static instance: FlutterManager; - - /** - * Gets the singleton instance of FlutterManager. - * @returns The singleton FlutterManager instance - */ - static getInstance(): FlutterManager { - if (FlutterManager.instance == null) { - FlutterManager.instance = new FlutterManager(); - } - return FlutterManager.instance; - } - - private flutterViewList = new Map(); - private flutterViewIndex = 1; - private uiAbilityList = new Array(); - private windowStageList = new Map(); - private mFullScreenListener: FullScreenListener = new DefaultFullScreenListener(); - - private dragEnterCbId: number = 1; - private dragMoveCbId: number = 1; - private dragLeaveCbId: number = 1; - private dropCbId: number = 1; - - private dragEnterCbs: HashMap = new HashMap(); - private dragMoveCbs: HashMap = new HashMap(); - private dragLeaveCbs: HashMap = new HashMap(); - private dropCbs: HashMap = new HashMap(); - - private getValuesFromMap(map: HashMap): List { - let list: List = new List(); - map.forEach((value, key) => { - list.add(value); - }); - return list; - } - - /** - * Gets all drag enter callbacks. - * @returns A list of drag enter callbacks - */ - getDragEnterCbs(): List { - return this.getValuesFromMap(this.dragEnterCbs); - } - - /** - * Gets all drag move callbacks. - * @returns A list of drag move callbacks - */ - getDragMoveCbs(): List { - return this.getValuesFromMap(this.dragMoveCbs); - } - - /** - * Gets all drag leave callbacks. - * @returns A list of drag leave callbacks - */ - getDragLeaveCbs(): List { - return this.getValuesFromMap(this.dragLeaveCbs); - } - - /** - * Gets all drop callbacks. - * @returns A list of drop callbacks - */ - getDropCbs(): List { - return this.getValuesFromMap(this.dropCbs); - } - - /** - * Adds a drag enter callback. - * @param callback - The drag enter callback - * @returns The callback ID - */ - addDragEnterCb(callback: DragDropCallback): number { - this.dragEnterCbs.set(this.dragEnterCbId, callback); - return this.dragEnterCbId++; - } - - /** - * Adds a drag move callback. - * @param callback - The drag move callback - * @returns The callback ID - */ - addDragMoveCb(callback: DragDropCallback): number { - this.dragMoveCbs.set(this.dragMoveCbId, callback); - return this.dragMoveCbId++; - } - - /** - * Adds a drag leave callback. - * @param callback - The drag leave callback - * @returns The callback ID - */ - addDragLeaveCb(callback: DragDropCallback): number { - this.dragLeaveCbs.set(this.dragLeaveCbId, callback); - return this.dragLeaveCbId++; - } - - /** - * Adds a drop callback. - * @param callback - The drop callback - * @returns The callback ID - */ - addDropCb(callback: DragDropCallback): number { - this.dropCbs.set(this.dropCbId, callback); - return this.dropCbId++; - } - - /** - * Removes a drag enter callback. - * @param id - The callback ID to remove - */ - removeDragEnterCb(id: number) { - this.dragEnterCbs.remove(id); - } - - /** - * Removes a drag move callback. - * @param id - The callback ID to remove - */ - removeDragMoveCb(id: number) { - this.dragMoveCbs.remove(id); - } - - /** - * Removes a drag leave callback. - * @param id - The callback ID to remove - */ - removeDragLeaveCb(id: number) { - this.dragLeaveCbs.remove(id); - } - - /** - * Removes a drop callback. - * @param id - The callback ID to remove - */ - removeDropCb(id: number) { - this.dropCbs.remove(id); - } - - /** - * Pushes a UIAbility to the list. - * @param uiAbility - The UIAbility to add - */ - pushUIAbility(uiAbility: UIAbility) { - this.uiAbilityList.push(uiAbility); - } - - /** - * Removes a UIAbility from the list. - * @param uiAbility - The UIAbility to remove - */ - popUIAbility(uiAbility: UIAbility) { - let index = this.uiAbilityList.findIndex((item: UIAbility) => item == uiAbility) - if (index >= 0) { - this.uiAbilityList.splice(index, 1) - } - } - - /** - * Associates a WindowStage with a UIAbility. - * @param uiAbility - The UIAbility - * @param windowStage - The WindowStage to associate - */ - pushWindowStage(uiAbility: UIAbility, windowStage: window.WindowStage) { - this.windowStageList.set(uiAbility, windowStage) - } - - /** - * Removes the WindowStage association for a UIAbility. - * @param uiAbility - The UIAbility - */ - popWindowStage(uiAbility: UIAbility) { - this.windowStageList.delete(uiAbility) - } - - /** - * Gets the WindowStage for a UIAbility. - * @param uiAbility - The UIAbility - * @returns The associated WindowStage - */ - getWindowStage(uiAbility: UIAbility): window.WindowStage { - return this.windowStageList.get(uiAbility)!! - } - - /** - * Gets a UIAbility by context, or returns the first one if no context is provided. - * @param context - Optional context to search for - * @returns The UIAbility instance - */ - getUIAbility(context?: Context): UIAbility { - if (!context && this.uiAbilityList.length > 0) { - return this.uiAbilityList[0]; - } - return this.uiAbilityList.find((item: UIAbility) => item.context == context)!! - } - - /** - * Checks if a FlutterView exists with the given ID. - * @param viewId - The view ID to check - * @returns True if the view exists, false otherwise - */ - hasFlutterView(viewId: string): boolean { - return this.flutterViewList.has(viewId); - } - - /** - * Gets a FlutterView by ID. - * @param viewId - The view ID - * @returns The FlutterView instance, or null if not found - */ - getFlutterView(viewId: string): FlutterView | null { - return this.flutterViewList.get(viewId) ?? null; - } - - /** - * Gets all FlutterView instances. - * @returns A map of all FlutterView instances by ID - */ - getFlutterViewList(): Map { - return this.flutterViewList; - } - - /** - * Stores or removes a FlutterView in the list. - * @param viewId - The view ID - * @param flutterView - The FlutterView instance, or undefined to remove - */ - private putFlutterView(viewId: string, flutterView?: FlutterView): void { - if (flutterView != null) { - this.flutterViewList.set(viewId, flutterView); - } else { - this.flutterViewList.delete(viewId); - } - } - - /** - * Creates a new FlutterView instance. - * It's suggested to keep 'oh_flutter_' as the prefix for xcomponent_id. - * Otherwise it might affect the performance. - * @param context - The context for creating the view - * @returns A new FlutterView instance - */ - createFlutterView(context: Context): FlutterView { - let flutterView = new FlutterView(`oh_flutter_${this.flutterViewIndex++}`, context); - this.putFlutterView(flutterView.getId(), flutterView); - return flutterView; - } - - /** - * Gets the next FlutterView ID that will be used. - * @param idOffset - Optional offset to add to the index - * @returns The next FlutterView ID string - */ - getNextFlutterViewId(idOffset: number = 0): string { - return `oh_flutter_${this.flutterViewIndex + idOffset}`; - } - - /** - * Clears all FlutterView instances. - */ - clear(): void { - this.flutterViewList.clear(); - } - - /** - * Sets the full screen listener. - * @param listener - The FullScreenListener instance - */ - setFullScreenListener(listener: FullScreenListener) { - this.mFullScreenListener = listener - } - - /** - * Gets the full screen listener. - * @returns The FullScreenListener instance - */ - getFullScreenListener(): FullScreenListener { - return this.mFullScreenListener; - } - - /** - * Sets whether to use full screen mode. - * @param use - Whether to use full screen - * @param context - Optional context - */ - setUseFullScreen(use: boolean, context?: Context | null | undefined) { - this.mFullScreenListener.setUseFullScreen(use, context); - } - - /** - * Checks if full screen mode is enabled. - * @returns True if full screen is enabled, false otherwise - */ - useFullScreen(): boolean { - return this.mFullScreenListener.useFullScreen(); - } - - /** - * Deletes a FlutterView from the list. - * @param viewId - The view ID - * @param flutterView - Optional FlutterView instance to verify - */ - deleteFlutterView(viewId: string, flutterView?: FlutterView): void { - if (flutterView != null) { - this.flutterViewList.delete(viewId); - } - } - - /** - * Get window ID for notifyPageChanged. - * @param context The context to get UIAbility - * @returns The window ID, or 0 if failed - */ - getWindowId(context: common.Context): number { - try { - const uiAbility = this.getUIAbility(context); - if (uiAbility == null) { - Log.e(TAG, "getWindowId: uiAbility is null"); - return 0; - } - const windowStage = this.getWindowStage(uiAbility); - if (windowStage == null) { - Log.e(TAG, "getWindowId: windowStage is null"); - return 0; - } - const mainWindow = windowStage.getMainWindowSync(); - if (mainWindow == null) { - Log.e(TAG, "getWindowId: mainWindow is null"); - return 0; - } - // get windowID for notifyPageChanged. - const windowId = mainWindow.getWindowProperties()?.id ?? 0; - return windowId; - } catch (error) { - Log.e(TAG, "getWindowId failed: " + JSON.stringify(error)); - } - return 0; - } -} - -/** - * Interface for drag and drop callbacks. - */ -export interface DragDropCallback { - /** - * Handles a drag and drop event. - * @param event - The drag event - * @param extraParams - Additional parameters - */ - do(event: DragEvent, extraParams: string): void; -} - -/** - * Interface for full screen state management. - */ -export interface FullScreenListener { - /** - * Checks if full screen mode is enabled. - * @returns True if full screen is enabled, false otherwise - */ - useFullScreen(): boolean; - - /** - * Sets whether to use full screen mode. - * @param useFullScreen - Whether to use full screen - * @param context - Optional context - */ - setUseFullScreen(useFullScreen: boolean, context?: Context | null | undefined): void; - - /** - * Called when the screen state changes. - * @param data - The window status type - */ - onScreenStateChanged(data: window.WindowStatusType): void; -} - -/** - * Default implementation of FullScreenListener. - */ -export class DefaultFullScreenListener implements FullScreenListener { - private fullScreen: boolean = true; - private skipCheck: boolean = false; - - /** - * Checks if full screen mode is enabled. - * @returns True if full screen is enabled, false otherwise - */ - useFullScreen(): boolean { - return this.fullScreen; - } - - /** - * Sets whether to use full screen mode. - * @param useFullScreen - Whether to use full screen - * @param context - Optional context - */ - setUseFullScreen(useFullScreen: boolean, context?: Context | null | undefined): void { - this.fullScreen = useFullScreen; - this.skipCheck = true; - - context = context??getContext(this); - const currentWindow = FlutterManager.getInstance() - .getWindowStage(FlutterManager.getInstance().getUIAbility(context)); - - currentWindow.getMainWindowSync().setWindowLayoutFullScreen(useFullScreen); - - if (deviceInfo.deviceType === '2in1' && deviceInfo.sdkApiVersion >= 14 && useFullScreen) { - currentWindow.getMainWindowSync().maximize(window.MaximizePresentation.ENTER_IMMERSIVE); - } - - Log.i(TAG, "WindowLayoutFullScreen is on") - } - - /** - * Called when the screen state changes. - * @param data - The window status type - */ - onScreenStateChanged(data: window.WindowStatusType): void { - if (this.skipCheck) { - Log.i(TAG, "onScreenStateChanged: skipCheck is on, WindowStatusType = " + data) - return; - } - switch (data) { - case window.WindowStatusType.FULL_SCREEN: - - case window.WindowStatusType.SPLIT_SCREEN: - case window.WindowStatusType.FLOATING: - case window.WindowStatusType.MAXIMIZE: - this.fullScreen = true; - Log.i(TAG, "onScreenStateChanged: fullScreen = true") - break; - default: - this.fullScreen = false; - Log.i(TAG, "onScreenStateChanged: fullScreen = false") - break; - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterPage.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterPage.ets deleted file mode 100644 index a1b2925..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterPage.ets +++ /dev/null @@ -1,342 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import Log from '../../util/Log'; -import { FlutterView } from '../../view/FlutterView'; -import FlutterManager from './FlutterManager'; -import { DVModel, DVModelChildren, DynamicView } from '../../view/DynamicView/dynamicView'; -import Any from '../../plugin/common/Any'; -import deviceInfo from '@ohos.deviceInfo'; -import flutter from 'libflutter.so'; -const TAG = "FlutterPage"; - - -/** - * Basic page component that hosts XComponent for Flutter rendering. - * This component handles the display of Flutter content within an OpenHarmony page. - */ -@Component -export struct FlutterPage { - /** Safe area edges to expand, or undefined for default. */ - @Prop safeAreaEdges: SafeAreaEdge[] | undefined = []; - /** Safe area types to expand, or undefined for default. */ - @Prop safeAreaTypes: SafeAreaType[] | undefined = []; - /** The unique identifier for the XComponent view. */ - @Prop viewId: string = "" - /** The XComponent type for rendering. */ - @Prop xComponentType: XComponentType = XComponentType.SURFACE - /** - * renderFit under XComponent has a default setting of RESIZE_FILL. - * If the size of XComponent may change, this property needs to be passed in and set to a size-preserving property, - * such as TOP_LEFT. - */ - @Prop xComponentRenderFit: RenderFit = RenderFit.RESIZE_FILL; - - /** - * A switch for enabling the frame cache. - * When it is true, one frame of response latency will be increased in exchange for higher smoothness, - * and occasional timeouts in rendering frame submissions will not result in frame dropping. - */ - @Prop enableFrameCacheForSmooth: boolean = true; - - /** - * Empty builder function used as default. - */ - @Builder - doNothingBuilder() { - } - - defaultFocusOnTouch = false; - - @BuilderParam splashScreenView: () => void = this.doNothingBuilder; - - /** - * Default page builder that displays Flutter content with XComponent. - */ - @Builder - defaultPage() { - Stack() { - ForEach(this.rootDvModel!!, (child: ESObject) => { - DynamicView({ - model: child as DVModel, - params: child.params, - events: child.events, - children: child.children, - customBuilder: child.builder - }) - }, (child: ESObject) => `${child.id_}`) - - Text("").id("unfocus-xcomponent-node").focusable(true) - - XComponent({ id: this.viewId, type: this.xComponentType, libraryname: 'flutter' }) - .id(this.viewId) - .focusable(true) - .focusOnTouch(this.defaultFocusOnTouch) - .onLoad((context) => { - this.flutterView?.onSurfaceCreated(); - // Callback is triggered when the xcomponent window is partially visible or completely hidden. - if (deviceInfo.sdkApiVersion < 15) { - this.getUIContext()?.getAttachedFrameNodeById(this.viewId)?.commonEvent.setOnVisibleAreaApproximateChange( - { ratios: [0.0, 1.0], expectedUpdateInterval: 0 }, - (isExpanding: boolean, currentRatio: number) => { - if (isExpanding) { - Log.i(TAG, "setOnVisibleAreaApproximateChange -> xcomponentId: " + this.viewId + - " isExpanding: " + isExpanding + " ratio: " + currentRatio); - flutter.nativeUpdateCurrentXComponentId(this.viewId); - } - } - ) - } - Log.d(TAG, "XComponent onLoad "); - }) - .onDestroy(() => { - Log.d(TAG, "XComponent onDestroy "); - this.flutterView?.onSurfaceDestroyed() - }) - .renderFit(this.xComponentRenderFit) - .backgroundColor(this.firstFrameDisplayed && this.xComponentRenderFit == RenderFit.RESIZE_FILL ? - this.xComponentColor : Color.Transparent) - .expandSafeArea(this.safeAreaTypes, this.safeAreaEdges) - if (this.showSplashScreen) { - this.splashScreenView(); - } - } - .defaultFocus(true) - .onKeyPreIme((event: KeyEvent) => { - return this.flutterView?.onKeyPreIme(event) ?? false; - }) - .onKeyEvent((event: KeyEvent) => { - return this.flutterView?.onKeyEvent(event) ?? false; - }) - .onDragEnter((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDragEnterCbs().forEach(dragEnterCb => { - dragEnterCb.do(event, extraParams); - }); - Log.d(TAG, "onDragEnter"); - }) - .onDragMove((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDragMoveCbs().forEach(dragMoveCb => { - dragMoveCb.do(event, extraParams); - }); - Log.d(TAG, "onDragMove"); - }) - .onDragLeave((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDragLeaveCbs().forEach(dragLeaveCb => { - dragLeaveCb.do(event, extraParams); - }); - Log.d(TAG, "onDragLeave"); - }) - .onDrop((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDropCbs().forEach(dropCb => { - dropCb.do(event, extraParams); - }); - Log.d(TAG, "onDrop"); - }) - } - - /** - * Page builder that supports mouse wheel gestures. - */ - @Builder - mouseWheelPage() { - Stack() { - ForEach(this.rootDvModel!!, (child: Any) => { - DynamicView({ - model: child as DVModel, - params: child.params, - events: child.events, - children: child.children, - customBuilder: child.builder - }) - }, (child: ESObject) => `${child.id_}`) - - Text("").id("unfocus-xcomponent-node").focusable(true) - - XComponent({ id: this.viewId, type: this.xComponentType, libraryname: 'flutter' }) - .id(this.viewId) - .focusable(true) - .focusOnTouch(this.defaultFocusOnTouch) - .onLoad((context) => { - this.flutterView?.onSurfaceCreated(); - // Callback is triggered when the xcomponent window is partially visible or completely hidden. - if (deviceInfo.sdkApiVersion < 15) { - this.getUIContext()?.getAttachedFrameNodeById(this.viewId)?.commonEvent.setOnVisibleAreaApproximateChange( - { ratios: [0.0, 1.0], expectedUpdateInterval: 0 }, - (isExpanding: boolean, currentRatio: number) => { - if (isExpanding) { - Log.i(TAG, "setOnVisibleAreaApproximateChange -> xcomponentId: " + this.viewId + - " isExpanding: " + isExpanding + " ratio: " + currentRatio); - flutter.nativeUpdateCurrentXComponentId(this.viewId); - } - } - ) - } - Log.d(TAG, "XComponent onLoad "); - }) - .onDestroy(() => { - Log.d(TAG, "XComponent onDestroy "); - this.flutterView?.onSurfaceDestroyed() - }) - .renderFit(this.xComponentRenderFit) - .backgroundColor(this.firstFrameDisplayed && this.xComponentRenderFit == RenderFit.RESIZE_FILL ? - this.xComponentColor : Color.Transparent) - .expandSafeArea(this.safeAreaTypes, this.safeAreaEdges) - - if (this.showSplashScreen) { - this.splashScreenView(); - } - } - .defaultFocus(true) - .onKeyPreIme((event: KeyEvent) => { - return this.flutterView?.onKeyPreIme(event) ?? false; - }) - .onKeyEvent((event: KeyEvent) => { - this.flutterView?.onKeyEvent(event) - }) - .onDragEnter((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDragEnterCbs().forEach(dragEnterCb => { - dragEnterCb.do(event, extraParams); - }); - Log.d(TAG, "onDragEnter"); - }) - .onDragMove((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDragMoveCbs().forEach(dragMoveCb => { - dragMoveCb.do(event, extraParams); - }); - Log.d(TAG, "onDragMove"); - }) - .onDragLeave((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDragLeaveCbs().forEach(dragLeaveCb => { - dragLeaveCb.do(event, extraParams); - }); - Log.d(TAG, "onDragLeave"); - }) - .onDrop((event: DragEvent, extraParams: string) => { - FlutterManager.getInstance().getDropCbs().forEach(dropCb => { - dropCb.do(event, extraParams); - }); - Log.d(TAG, "onDrop"); - }) - .gesture( - PanGesture(this.panOption) - .onActionStart((event: GestureEvent) => { - this.flutterView?.onMouseWheel("actionStart", event); - }) - .onActionUpdate((event: GestureEvent) => { - this.flutterView?.onMouseWheel("actionUpdate", event); - }) - .onActionEnd((event: GestureEvent) => { - this.flutterView?.onMouseWheel("actionEnd", event); - }) - ) - } - - /** Whether to show the splash screen. */ - @State showSplashScreen: boolean = true; - /** - * To address the black(or other color set by usr) flashing frame when switching between ArkUI and Flutter pages, - * the background color should be kept transparent until the onFirstFrame is called. - * When the window size changes, modifying the renderFit property in the relevant callback does not take effect immediately. - * The first frame will use the old renderFit property and the old background color, resulting in visual artifacts (such as stretching or a black screen). - * Therefore, we cannot automatically change the relevant properties through state variables at this time. - */ - @State firstFrameDisplayed: boolean = false; - /** Background color for the XComponent. */ - @State xComponentColor: Color = Color.Black - - /** Whether to check for full screen mode. */ - @State checkFullScreen: boolean = true; - /** Whether to check for keyboard visibility. */ - @State checkKeyboard: boolean = true; - /** Whether to check for gesture navigation. */ - @State checkGesture: boolean = true; - /** Whether to enable mouse wheel gesture support. */ - @State checkMouseWheel: boolean = true; - /** Whether to check for AI bar. */ - @State checkAiBar: boolean = true; - /** Top padding value, or undefined if not set. */ - @Prop @Watch("onPaddingChange")paddingTop?: number = undefined; - /** Storage link for node width. */ - @StorageLink('nodeWidth') storageLinkWidth: number = 0; - /** Storage link for node height. */ - @StorageLink('nodeHeight') storageLinkHeight: number = 0; - - /** Root dynamic view model children, or undefined if not set. */ - @State rootDvModel: DVModelChildren | undefined = undefined - - private flutterView?: FlutterView | null - private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }); - - /** - * Called when the page is about to appear. - * Initializes the FlutterView and sets up listeners and callbacks. - */ - aboutToAppear() { - this.flutterView = FlutterManager.getInstance().getFlutterView(this.viewId); - this.flutterView?.addFirstFrameListener(this) - this.flutterView?.addFirstPreloadFrameListener(this) - - // api18开始支持getDistance()接口 - if (deviceInfo.sdkApiVersion >= 18) { - this.flutterView?.setTouchSlopCallbackValue(() => { - return this.panOption.getDistance() - }) - } - - this.flutterView?.setCheckFullScreen(this.checkFullScreen) - this.flutterView?.setCheckKeyboard(this.checkKeyboard) - this.flutterView?.setCheckGesture(this.checkGesture) - this.flutterView?.setPaddingTop(this.paddingTop) - this.flutterView?.setCheckAiBar(this.checkAiBar) - this.flutterView?.enableFrameCache(this.enableFrameCacheForSmooth); - - this.rootDvModel = this.flutterView!!.getDVModel().children - } - - /** - * Called when the page is about to disappear. - * Removes frame listeners to clean up resources. - */ - aboutToDisappear() { - this.flutterView?.removeFirstFrameListener(this); - this.flutterView?.removeFirstPreloadFrameListener(this) - } - - /** - * Called when the first frame is displayed. - * Hides the splash screen and marks the first frame as displayed. - */ - onFirstFrame() { - this.showSplashScreen = false; - this.firstFrameDisplayed = true; - } - - /** - * Called when the first preload frame is displayed. - */ - onFirstPreloadFrame() { - } - - /** - * Called when padding changes. - * Updates the FlutterView's padding. - */ - onPaddingChange() { - this.flutterView?.setPaddingTop(this.paddingTop); - } - - /** - * Builds the page UI. - * Selects between mouse wheel page and default page based on configuration. - */ - build() { - if (this.checkMouseWheel) { - this.mouseWheelPage(); - } else { - this.defaultPage(); - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyData.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyData.ets deleted file mode 100644 index 8c43af1..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyData.ets +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2021-2025 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -import util from '@ohos.util' - -/** - * Represents key event data for communication between OpenHarmony and Flutter. - * This class can serialize and deserialize key data to/from binary format. - */ -export default class KeyData { - private static TAG = "KeyData"; - public static CHANNEL = "flutter/keydata"; - // If this value changes, update the code in the following files: - // - // * key_data.h (kKeyDataFieldCount) - // * platform_dispatcher.dart (_kKeyDataFieldCount) - private static FIELD_COUNT: number = 6; - private static BYTES_PER_FIELD: number = 8; - /** Timestamp of the key event. */ - public timestamp: number = 0; - /** Type of the key event (KDOWN, KUP, or KREPEAT). */ - public type: Type = Type.KDOWN; - /** Physical key code. */ - public physicalKey: number = 0; - /** Logical key code. */ - public logicalKey: number = 0; - /** Whether this key event was synthesized. */ - public isSynthesized: boolean = false; - /** Type of the input device. */ - public deviceType: DeviceType = DeviceType.KKEYBOARD; - /** Character representation of the key, or null if not applicable. */ - public character: string | null = null; - - /** - * Constructs a new KeyData instance. - * @param buffer - Optional ArrayBuffer to deserialize key data from - */ - constructor(buffer?: ArrayBuffer) { - if (buffer !== undefined) { - const view = new DataView(buffer); - let offset = 0; - - const decoder = new util.TextDecoder("utf-8"); - const charSize = Number(view.getBigInt64(offset, true)); - offset += 8; - - this.timestamp = Number(view.getBigInt64(offset, true)); - offset += 8; - - this.type = Number(view.getBigInt64(offset, true)) as Type; - offset += 8; - - this.physicalKey = Number(view.getBigInt64(offset, true)); - offset += 8; - - this.logicalKey = Number(view.getBigInt64(offset, true)); - offset += 8; - - this.isSynthesized = view.getBigInt64(offset, true) === BigInt(1); - offset += 8; - - this.deviceType = Number(view.getBigInt64(offset, true)) as DeviceType; - offset += 8; - - if (offset + charSize !== buffer.byteLength) { - throw new Error("KeyData corruption: String length does not match remaining bytes in buffer"); - } - - if (charSize != 0) { - const strBytes = new Uint8Array(buffer, offset, charSize); - this.character = decoder.decode(strBytes); - } - } - } - - /** - * Serializes this KeyData instance to a binary ArrayBuffer. - * @returns The serialized key data as an ArrayBuffer - */ - public toBytes(): ArrayBuffer { - const encoder = new util.TextEncoder("utf-8"); - const encodedCharBytes = this.character == null ? null : encoder.encode(this.character); - const charSize = this.character == null ? 0 : this.character.length; - - const totalBytes = (KeyData.FIELD_COUNT + 1) * KeyData.BYTES_PER_FIELD + charSize; - const buffer = new ArrayBuffer(totalBytes); - const view = new DataView(buffer); - let offset = 0; - - view.setBigInt64(offset, BigInt(charSize), true); - offset += 8; - - view.setBigInt64(offset, BigInt(this.timestamp), true); - offset += 8; - - view.setBigInt64(offset, BigInt(this.type), true); - offset += 8; - - view.setBigInt64(offset, BigInt(this.physicalKey), true); - offset += 8; - - view.setBigInt64(offset, BigInt(this.logicalKey), true); - offset += 8; - - view.setBigInt64(offset, this.isSynthesized ? BigInt(1) : BigInt(0), true); - offset += 8; - - view.setBigInt64(offset, BigInt(this.deviceType), true); - offset += 8; - - if (encodedCharBytes != null) { - new Uint8Array(buffer, offset, charSize).set(encodedCharBytes); - } - - return buffer; - } -} - -/** - * Key event types. - */ -export enum Type { - KDOWN = 0, - KUP, - KREPEAT -} - -/** - * Types of input devices. - */ -export enum DeviceType { - KKEYBOARD = 0, - KDIRECTIONALPAD, - KGAMEPAD, - KJOYSTICK, - KHDMI -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyEmbedderResponder.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyEmbedderResponder.ets deleted file mode 100644 index 2d0440a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyEmbedderResponder.ets +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (c) 2021-2025 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -import { BinaryMessenger, BinaryReply } from '../../plugin/common/BinaryMessenger'; -import KeyData, { Type, DeviceType } from './KeyData'; -import { Responder } from './KeyboardManager'; -import KeyboardMap, { KeyPair, ModifierGoal } from './KeyboardMap'; -import Log from '../../util/Log'; - -/** - * Task runner for executing event tasks asynchronously. - */ -class EventTaskRunner { - private tasks: Array<() => void> = []; - - /** - * Constructs a new EventTaskRunner instance. - */ - constructor() { - } - - /** - * Adds a task to be executed later. - * @param task - The task function to add - */ - public addTask(task: () => void): void { - this.tasks.push(task); - } - - /** - * Runs all queued tasks. - */ - public runTasks(): void { - this.tasks.forEach(task => task()); - } -} - -/** - * Responder for handling key events using the embedder API. - * This class converts OpenHarmony key events to Flutter key data format and sends them to Flutter. - */ -export default class KeyEmbedderResponder implements Responder { - private static TAG = "KeyEmbedderResponder"; - private messenger: BinaryMessenger; - private pressingRecords: Map = new Map(); - - /** - * Constructs a new KeyEmbedderResponder instance. - * @param binaryMessenger - The BinaryMessenger for sending key events to Flutter - */ - constructor(binaryMessenger: BinaryMessenger) { - this.messenger = binaryMessenger; - } - - private keyOfPlane(key: number, plane: number): number { - return plane | (key & KeyboardMap.kValueMask); - } - - private getEventType(event: KeyEvent): Type { - let physicalKey: number = this.getPhysicalKey(event); - let isPressed: boolean = this.pressingRecords.has(physicalKey); - switch (event.type) { - case KeyType.Down: - return isPressed ? Type.KREPEAT : Type.KDOWN; - break; - case KeyType.Up: - return Type.KUP; - break; - default: - throw new Error("getEventType: Unexpected event type"); - } - } - - private getLogicalKey(event: KeyEvent): number { - let keyCode: number = event.keyCode; - let logicalKey: number | undefined = KeyboardMap.toLogicalKey.get(keyCode); - if (logicalKey !== undefined) { - return logicalKey; - } - return this.keyOfPlane(keyCode, KeyboardMap.kOhosPlane); - } - - private getPhysicalKey(event: KeyEvent): number { - let keyCode: number = event.keyCode; - let physicalKey: number | undefined = KeyboardMap.toPhysicalKey.get(keyCode); - if (physicalKey !== undefined) { - return physicalKey; - } - return this.keyOfPlane(keyCode, KeyboardMap.kOhosPlane); - } - - /** - * Updates the pressing keys record. - * @param physicalKey - The physical key code - * @param logicalKey - The logical key code, or null if the key is released - */ - updatePressingKeys(physicalKey: number, logicalKey: number | null): void { - if (logicalKey != null) { // press - if (this.pressingRecords.has(physicalKey)) { - Log.e(KeyEmbedderResponder.TAG, "updatePressingKeys adding nonempty key"); - } - this.pressingRecords.set(physicalKey, logicalKey); - } else { // release - if (!this.pressingRecords.has(physicalKey)) { - Log.e(KeyEmbedderResponder.TAG, "updatePressingKeys deleting empty key"); - } - this.pressingRecords.delete(physicalKey); - } - } - - /** - * Synchronizes modifier key states to match expected states. - * @param goal - The modifier goal to synchronize - * @param truePressed - Whether the modifier key is actually pressed - * @param logicalKey - The logical key code - * @param physicalKey - The physical key code - * @param event - The key event - * @param postSyncEvents - Task runner for post-synchronization events - */ - synchronizeModifierKey(goal: ModifierGoal, - truePressed: boolean, - logicalKey: number, - physicalKey: number, - event: KeyEvent, - postSyncEvents: EventTaskRunner) { - let nowStates: boolean[] = new Array(goal.keys.length); - let expectedPreStates: boolean[] = new Array(goal.keys.length); - let postAnyPressed: boolean = false; - - for (let keyIdx = 0; keyIdx < goal.keys.length; keyIdx += 1) { - let key: KeyPair = goal.keys[keyIdx]; - nowStates[keyIdx] = this.pressingRecords.has(key.physicalKey); - if (key.logicalKey == logicalKey) { - switch (this.getEventType(event)) { - case Type.KDOWN: - expectedPreStates[keyIdx] = false; - postAnyPressed = true; - if (!truePressed) { - postSyncEvents.addTask(() => { - this.synthesizeEvent(false, event.timestamp, logicalKey, physicalKey); - }); - } - break; - case Type.KUP: - expectedPreStates[keyIdx] = nowStates[keyIdx]; - break; - case Type.KREPEAT: - expectedPreStates[keyIdx] = nowStates[keyIdx]; - postAnyPressed = true; - if (!truePressed) { - postSyncEvents.addTask(() => { - this.synthesizeEvent(false, event.timestamp, logicalKey, physicalKey); - }); - } - break; - } - } else { - postAnyPressed = postAnyPressed || nowStates[keyIdx]; - } - } - - if (truePressed) { - for (let keyIdx = 0; keyIdx < goal.keys.length; keyIdx += 1) { - if (expectedPreStates[keyIdx] !== undefined) { - continue; - } - if (postAnyPressed) { - expectedPreStates[keyIdx] = nowStates[keyIdx]; - } else { - expectedPreStates[keyIdx] = true; - postAnyPressed = true; - } - } - if (!postAnyPressed) { - expectedPreStates[0] = true; - } - } else { - for (let keyIdx = 0; keyIdx < goal.keys.length; keyIdx += 1) { - if (expectedPreStates[keyIdx] !== undefined) { - continue; - } - expectedPreStates[keyIdx] = false; - } - } - - for (let keyIdx = 0; keyIdx < goal.keys.length; keyIdx += 1) { - if (expectedPreStates[keyIdx] != nowStates[keyIdx]) { - let key: KeyPair = goal.keys[keyIdx]; - this.synthesizeEvent(expectedPreStates[keyIdx], event.timestamp, - key.logicalKey, key.physicalKey); - } - } - } - - /** - * Synthesizes a key event. - * @param isDown - Whether the key is down - * @param timestamp - The event timestamp - * @param logicalKey - The logical key code - * @param physicalKey - The physical key code - */ - synthesizeEvent(isDown: boolean, timestamp: number, - logicalKey: number, physicalKey: number) { - const data: KeyData = new KeyData(); - data.timestamp = timestamp; - data.type = isDown ? Type.KDOWN : Type.KUP; - data.logicalKey = logicalKey; - data.physicalKey = physicalKey; - data.character = null; - data.isSynthesized = true; - data.deviceType = DeviceType.KKEYBOARD; - if (physicalKey != 0 && logicalKey != 0) { - this.updatePressingKeys(physicalKey, isDown ? logicalKey : null); - } - - this.sendKeyEvent(data); - } - - /** - * Sends a key event to Flutter. - * @param data - The KeyData to send - */ - sendKeyEvent(data: KeyData) { - this.messenger.send(KeyData.CHANNEL, data.toBytes()); - } - - /** - * Handles a key event from OpenHarmony. - * @param event - The key event to handle - * @returns Whether the event was handled - */ - handleKeyEvent(event: KeyEvent): boolean { - if (event.keyCode == 0) { - return false; - } - - let physicalKey: number = this.getPhysicalKey(event); - let logicalKey: number = this.getLogicalKey(event); - - let postSyncEvents: EventTaskRunner = new EventTaskRunner(); - - for (let goalIdx = 0; goalIdx < KeyboardMap.modifierGoals.length; goalIdx += 1) { - let goal: ModifierGoal = KeyboardMap.modifierGoals[goalIdx]; - if (event.getModifierKeyState != undefined) { - this.synchronizeModifierKey( - goal, - event.getModifierKeyState([goal.name]), - logicalKey, - physicalKey, - event, - postSyncEvents - ); - } - } - - let isDownEvent: boolean; - switch (event.type) { - case KeyType.Down: - isDownEvent = true; - break; - case KeyType.Up: - isDownEvent = false; - break; - default: - isDownEvent = false; - } - - let type: Type; - let lastLogicalKey: number | undefined = this.pressingRecords.get(physicalKey); - if (isDownEvent) { - if (lastLogicalKey === undefined) { - type = Type.KDOWN; - } else { - /* Nothing about repeat found in KeyEvent, so if isDownEvent and the key is - * currently pressed, take this event as a KREPEAT one. - */ - type = Type.KREPEAT; - } - } else { - if (lastLogicalKey === undefined) { - /* Ignore abrupt up events */ - return false; - } else { - type = Type.KUP; - } - } - - if (type != Type.KREPEAT) { - this.updatePressingKeys(physicalKey, isDownEvent ? logicalKey : null); - } - - const data: KeyData = new KeyData(); - data.timestamp = event.timestamp; - data.type = type; - data.physicalKey = physicalKey; - data.logicalKey = logicalKey; - data.character = null; - data.isSynthesized = false; - // no deviceType found in KeyEvent - data.deviceType = DeviceType.KKEYBOARD; - this.sendKeyEvent(data); - - postSyncEvents.runTasks(); - return true; - } - - /** - * Gets the currently pressed keys. - * @returns A map of physical key codes to logical key codes - */ - public getPressedKeys(): Map { - return new Map(this.pressingRecords); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyEventHandler.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyEventHandler.ets deleted file mode 100644 index 329a7b8..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyEventHandler.ets +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -import { HashMap } from '@kit.ArkTS'; -import deviceInfo from '@ohos.deviceInfo'; -import TextInputPlugin from '../../plugin/editing/TextInputPlugin'; -import Log from '../../util/Log'; -import { KeyCode } from '@kit.InputKit'; -import { ListenableEditingState } from '../../plugin/editing/ListenableEditingState'; - -const TAG = "KeyEventHandler"; - -/** - * Represents text for a key in normal and shift cases. - */ -class KeyText { - /** The text in normal case. */ - public normalCase: string; - /** The text in shift case. */ - public shiftCase: string; - - /** - * Constructs a new KeyText instance. - * @param normalCase - The text in normal case - * @param shiftCase - The text in shift case - */ - constructor(normalCase: string, shiftCase: string) { - this.normalCase = normalCase; - this.shiftCase = shiftCase; - } -} -; - -/** - * Handler for key events in emulator/hdc tool input scenarios. - * - * In the emulator/hdc tool input scenario, all keyevents will be passed to the onKeyEvent callback, - * so we need to insert the text to textInputPlugin ourselves. In other scenarios like phone/pc/ets, - * the input method will be responsible for inserting the text to textInputPlugin, consuming 'down' events - * and letting 'up' events go which will be captured by onKeyEvent. - * - * There is no need to process the status of the capslock button. Because in the scenario of the emulator/hdc tool, - * if capslock is pressed, os will send a 'shift' keyevent before the keyevent of the input key and we will insert - * uppercase characters correctly. - */ -export class KeyEventHandler { - - private static keyTextMap: Map = new Map([ - [KeyCode.KEYCODE_0, new KeyText('0', ')')], - [KeyCode.KEYCODE_1, new KeyText('1', '!')], - [KeyCode.KEYCODE_2, new KeyText('2', '@')], - [KeyCode.KEYCODE_3, new KeyText('3', '#')], - [KeyCode.KEYCODE_4, new KeyText('4', '$')], - [KeyCode.KEYCODE_5, new KeyText('5', '%')], - [KeyCode.KEYCODE_6, new KeyText('6', '^')], - [KeyCode.KEYCODE_7, new KeyText('7', '&')], - [KeyCode.KEYCODE_8, new KeyText('8', '*')], - [KeyCode.KEYCODE_9, new KeyText('9', '(')], - [KeyCode.KEYCODE_A, new KeyText('a', 'A')], - [KeyCode.KEYCODE_B, new KeyText('b', 'B')], - [KeyCode.KEYCODE_C, new KeyText('c', 'C')], - [KeyCode.KEYCODE_D, new KeyText('d', 'D')], - [KeyCode.KEYCODE_E, new KeyText('e', 'E')], - [KeyCode.KEYCODE_F, new KeyText('f', 'F')], - [KeyCode.KEYCODE_G, new KeyText('g', 'G')], - [KeyCode.KEYCODE_H, new KeyText('h', 'H')], - [KeyCode.KEYCODE_I, new KeyText('i', 'I')], - [KeyCode.KEYCODE_J, new KeyText('j', 'J')], - [KeyCode.KEYCODE_K, new KeyText('k', 'K')], - [KeyCode.KEYCODE_L, new KeyText('l', 'L')], - [KeyCode.KEYCODE_M, new KeyText('m', 'M')], - [KeyCode.KEYCODE_N, new KeyText('n', 'N')], - [KeyCode.KEYCODE_O, new KeyText('o', 'O')], - [KeyCode.KEYCODE_P, new KeyText('p', 'P')], - [KeyCode.KEYCODE_Q, new KeyText('q', 'Q')], - [KeyCode.KEYCODE_R, new KeyText('r', 'R')], - [KeyCode.KEYCODE_S, new KeyText('s', 'S')], - [KeyCode.KEYCODE_T, new KeyText('t', 'T')], - [KeyCode.KEYCODE_U, new KeyText('u', 'U')], - [KeyCode.KEYCODE_V, new KeyText('v', 'V')], - [KeyCode.KEYCODE_W, new KeyText('w', 'W')], - [KeyCode.KEYCODE_X, new KeyText('x', 'X')], - [KeyCode.KEYCODE_Y, new KeyText('y', 'Y')], - [KeyCode.KEYCODE_Z, new KeyText('z', 'Z')], - - [KeyCode.KEYCODE_GRAVE, new KeyText('`', '~')], - [KeyCode.KEYCODE_MINUS, new KeyText('-', '_')], - [KeyCode.KEYCODE_EQUALS, new KeyText('=', '+')], - [KeyCode.KEYCODE_LEFT_BRACKET, new KeyText('[', '{')], - [KeyCode.KEYCODE_RIGHT_BRACKET, new KeyText(']', '}')], - [KeyCode.KEYCODE_BACKSLASH, new KeyText('\\', '|')], - [KeyCode.KEYCODE_SEMICOLON, new KeyText(';', ':')], - [KeyCode.KEYCODE_APOSTROPHE, new KeyText('\'', '"')], - [KeyCode.KEYCODE_COMMA, new KeyText(',', '<')], - [KeyCode.KEYCODE_PERIOD, new KeyText('.', '>')], - [KeyCode.KEYCODE_SLASH, new KeyText('/', '?')], - [KeyCode.KEYCODE_SPACE, new KeyText(' ', ' ')], - - [KeyCode.KEYCODE_NUMPAD_0, new KeyText('0', '')], - [KeyCode.KEYCODE_NUMPAD_1, new KeyText('1', '')], - [KeyCode.KEYCODE_NUMPAD_2, new KeyText('2', '')], - [KeyCode.KEYCODE_NUMPAD_3, new KeyText('3', '')], - [KeyCode.KEYCODE_NUMPAD_4, new KeyText('4', '')], - [KeyCode.KEYCODE_NUMPAD_5, new KeyText('5', '')], - [KeyCode.KEYCODE_NUMPAD_6, new KeyText('6', '')], - [KeyCode.KEYCODE_NUMPAD_7, new KeyText('7', '')], - [KeyCode.KEYCODE_NUMPAD_8, new KeyText('8', '')], - [KeyCode.KEYCODE_NUMPAD_9, new KeyText('9', '')], - [KeyCode.KEYCODE_NUMPAD_DOT, new KeyText('.', '')], - [KeyCode.KEYCODE_NUMPAD_ADD, new KeyText('+', '')], - [KeyCode.KEYCODE_NUMPAD_SUBTRACT, new KeyText('-', '')], - [KeyCode.KEYCODE_NUMPAD_MULTIPLY, new KeyText('*', '')], - [KeyCode.KEYCODE_NUMPAD_DIVIDE, new KeyText('/', '')], - [KeyCode.KEYCODE_NUMPAD_EQUALS, new KeyText('=', '')], - ]); - private textInputPlugin?: TextInputPlugin; - - /** - * Constructs a new KeyEventHandler instance. - * @param textInputPlugin - The TextInputPlugin instance, optional - */ - constructor(textInputPlugin?: TextInputPlugin) { - this.textInputPlugin = textInputPlugin; - } - - /** - * Gets the text representation of a key event. - * @param event - The key event - * @returns The text string for the key, considering shift state - */ - getKeyText(event: KeyEvent) : string { - let keyText = KeyEventHandler.keyTextMap.get(event.keyCode); - if (keyText !== undefined) { - // Check if it's a letter key (A-Z) - const isLetter = event.keyCode >= KeyCode.KEYCODE_A && event.keyCode <= KeyCode.KEYCODE_Z; - // Use event.isCapsLockOn to check CapsLock state - const isCapsLockOn = event.isCapsLockOn !== undefined ? event.isCapsLockOn : false; - // Use getModifierKeyState to check Shift state - const isShiftPressed = this.getModifierKeyStateSafe(event, ['Shift']); - // If CapsLock is enabled, reverse the case for letter keys - if (isLetter && isCapsLockOn) { - // Shift + CapsLock = lowercase (reverses CapsLock effect) - return isShiftPressed ? keyText.normalCase : keyText.shiftCase; - } else { - // Normal case: Shift determines case - return isShiftPressed ? keyText.shiftCase : keyText.normalCase; - } - } - return ''; - } - - /** - * Starts a deletion operation. - * @param code - The key code for deletion - */ - startDeleting(code: number) { - this.textInputPlugin?.getEditingState().startDeleting(code); - } - - /** - * Ends a deletion operation. - * @param code - The key code for deletion - */ - endDeletion(code: number) { - this.textInputPlugin?.getEditingState().endDeletion(code); - } - - /** - * Helper method to safely execute an action on the editing state. - * @param action - The action to execute with the editing state - * @returns True if the action was executed, false otherwise - */ - private withEditingState(action: (editingState: ListenableEditingState) => void): boolean { - const editingState = this.textInputPlugin?.getEditingState(); - if (editingState) { - action(editingState); - return true; - } - return false; - } - - /** - * Safely gets the modifier key state from a key event. - * @param event - The key event - * @param modifierKeys - Array of modifier key names (e.g., ['Shift'], ['Ctrl'], ['Alt']) - * @returns True if the modifier key is pressed, false otherwise - */ - private getModifierKeyStateSafe(event: KeyEvent, modifierKeys: string[]): boolean { - if (!event.getModifierKeyState) { - return false; - } - try { - return event.getModifierKeyState(modifierKeys) || false; - } catch (e) { - const errorMsg = e instanceof Error ? e.message : String(e); - Log.e(TAG, `Failed to get modifier key state for ${modifierKeys.join(', ')}: ${errorMsg}`); - return false; - } - } - - /** - * Handles a key event and inserts text if appropriate. - * @param event - The key event to handle - */ - handleKeyEvent(event: KeyEvent) { - Log.i(TAG, JSON.stringify({ - "name": "handleKeyEvent", - "event": event - })); - if (event.type === KeyType.Down) { - switch (event.keyCode) { - case KeyCode.KEYCODE_ENTER: - case KeyCode.KEYCODE_NUMPAD_ENTER: { - // Handle Enter key (both regular and numpad), insert newline character - this.withEditingState((editingState) => { - editingState.handleInsertTextEvent('\n'); - Log.i(TAG, `ENTER: inserted newline`); - }); - break; - } - default: { - // Check if it's a numpad number key (0-9) - const isNumpadNumberKey = event.keyCode >= KeyCode.KEYCODE_NUMPAD_0 && event.keyCode <= KeyCode.KEYCODE_NUMPAD_9; - // Check if it's NUMPAD_DOT key - const isNumpadDotKey = event.keyCode === KeyCode.KEYCODE_NUMPAD_DOT; - - // Use event.isNumLockOn to check NumLock state - const isNumLockOn = event.isNumLockOn !== undefined ? event.isNumLockOn : true; // Default to true if not available - - // When NumLock is on, block numpad number keys (0-9) and NUMPAD_DOT, allow other special keys to input symbols - if ((isNumpadNumberKey || isNumpadDotKey) && !isNumLockOn) { - Log.i(TAG, `Numpad key ${event.keyCode} blocked: NumLock is off`); - return; - } - // Other special keys (NUMPAD_ADD, NUMPAD_SUBTRACT, etc.) can input symbols even when NumLock is off - - // Use getModifierKeyState to check Ctrl and Alt state - // Returns true if either Ctrl or Alt (or both) is pressed - const isCtrlPressed = this.getModifierKeyStateSafe(event, ['Ctrl']); - const isAltPressed = this.getModifierKeyStateSafe(event, ['Alt']); - const isCombinationMode = isCtrlPressed || isAltPressed; - - // Don't input characters (letters/numbers/symbols) when Ctrl/Alt keys are pressed or data is empty - if (!isCombinationMode && this.getKeyText(event)) { - this.withEditingState((editingState) => { - // Insert character - editingState.handleInsertTextEvent(this.getKeyText(event)); - }); - } - break; - } - } - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyboardManager.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyboardManager.ets deleted file mode 100644 index eb8213f..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyboardManager.ets +++ /dev/null @@ -1,87 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on KeyboardManager.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import TextInputPlugin from '../../plugin/editing/TextInputPlugin'; -import FlutterEngine from '../engine/FlutterEngine'; -import KeyEventChannel, { FlutterKeyEvent } from '../engine/systemchannels/KeyEventChannel'; -import KeyboardChannel from '../engine/systemchannels/KeyboardChannel'; -import KeyEmbedderResponder from './KeyEmbedderResponder'; -import { BinaryMessenger } from '../../plugin/common/BinaryMessenger'; -import { KeyEventHandler } from './KeyEventHandler'; -import HashSet from '@ohos.util.HashSet'; -import { KeyCode } from '@kit.InputKit'; - -/** - * Manages keyboard events and state for Flutter. - * This class coordinates between key event channels, keyboard channels, and text input handling. - */ -export default class KeyboardManager { - private keyEventChannel: KeyEventChannel | null = null; - private keyboardChannel: KeyboardChannel | null = null; - /** The key embedder responder for handling key events. */ - protected keyEmbedderResponder: KeyEmbedderResponder; - private keyEventHandler: KeyEventHandler; - - /** - * Constructs a new KeyboardManager instance. - * @param engine - The FlutterEngine instance - * @param textInputPlugin - The TextInputPlugin instance - */ - constructor(engine: FlutterEngine, textInputPlugin: TextInputPlugin) { - this.keyEventChannel = new KeyEventChannel(engine.dartExecutor); - this.keyboardChannel = new KeyboardChannel(engine.dartExecutor); - this.keyboardChannel.setKeyboardMethodHandler(this); - this.keyEmbedderResponder = new KeyEmbedderResponder(engine.dartExecutor); - this.keyEventHandler = new KeyEventHandler(textInputPlugin); - } - - /** - * Handles key events before they are processed by the input method editor. - * @param event - The key event - * @returns Whether the event was handled - */ - onKeyPreIme(event: KeyEvent) : boolean { - return false; - } - - /** - * Handles key events. - * @param event - The key event - * @returns Whether the event was handled - */ - onKeyEvent(event: KeyEvent) : boolean { - this.keyEmbedderResponder.handleKeyEvent(event); - - this.keyEventChannel?.sendFlutterKeyEvent(new FlutterKeyEvent(event), event.type == KeyType.Up, { - onFrameworkResponse: (isEventHandled: boolean): void => { - } - }) - this.keyEventHandler.handleKeyEvent(event); - return false; - } - - /** - * Gets the current keyboard state (pressed keys). - * @returns A map of pressed key codes - */ - public getKeyboardState(): Map { - return this.keyEmbedderResponder.getPressedKeys(); - } -} - -/** - * Interface for handling key events. - */ -export interface Responder { - /** - * Handles a key event. - * @param keyEvent - The key event to handle - */ - handleKeyEvent(keyEvent: KeyEvent): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyboardMap.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyboardMap.ets deleted file mode 100644 index 52b86e4..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/KeyboardMap.ets +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright (c) 2021-2025 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -/** - * Represents a pair of physical and logical key codes. - */ -export class KeyPair { - /** The physical key code. */ - public physicalKey: number; - /** The logical key code. */ - public logicalKey: number; - - /** - * Constructs a new KeyPair instance. - * @param physicalKey - The physical key code - * @param logicalKey - The logical key code - */ - constructor(physicalKey: number, logicalKey: number) { - this.physicalKey = physicalKey; - this.logicalKey = logicalKey; - } -} - -/** - * Represents a modifier key goal with associated key pairs. - */ -export class ModifierGoal { - /** The modifier name (e.g., "Ctrl", "Shift", "Alt"). */ - public name: string; - /** Array of KeyPair instances for this modifier. */ - public keys: KeyPair[]; - - /** - * Constructs a new ModifierGoal instance. - * @param name - The modifier name (e.g., "Ctrl", "Shift", "Alt") - * @param keys - Array of KeyPair instances for this modifier - */ - constructor(name: string, keys: KeyPair[]) { - this.name = name; - this.keys = keys; - } -} - -/** - * Maps OpenHarmony KeyCodes to Flutter LogicalKeys and PhysicalKeys. - * This class provides static mappings for keyboard key translation between OpenHarmony and Flutter. - */ -export default class KeyboardMap { - /** Map from OpenHarmony KeyCode to Flutter LogicalKey. */ - public static toLogicalKey: Map = - new Map([ - [0x000000007D0, 0x00000000030], // digit0 - [0x000000007D1, 0x00000000031], // digit1 - [0x000000007D2, 0x00000000032], // digit2 - [0x000000007D3, 0x00000000033], // digit3 - [0x000000007D4, 0x00000000034], // digit4 - [0x000000007D5, 0x00000000035], // digit5 - [0x000000007D6, 0x00000000036], // digit6 - [0x000000007D7, 0x00000000037], // digit7 - [0x000000007D8, 0x00000000038], // digit8 - [0x000000007D9, 0x00000000039], // digit9 - [0x000000007DA, 0x0000000002A], // asterisk - [0x000000007DB, 0x00000000023], // numberSign - [0x000000007DC, 0x00100000304], // arrowUp - [0x000000007DD, 0x00100000301], // arrowDown - [0x000000007DE, 0x00100000302], // arrowLeft - [0x000000007DF, 0x00100000303], // arrowRight - [0x000000007E1, 0x00000000061], // keyA - [0x000000007E2, 0x00000000062], // keyB - [0x000000007E3, 0x00000000063], // keyC - [0x000000007E4, 0x00000000064], // keyD - [0x000000007E5, 0x00000000065], // keyE - [0x000000007E6, 0x00000000066], // keyF - [0x000000007E7, 0x00000000067], // keyG - [0x000000007E8, 0x00000000068], // keyH - [0x000000007E9, 0x00000000069], // keyI - [0x000000007EA, 0x0000000006A], // keyJ - [0x000000007EB, 0x0000000006B], // keyK - [0x000000007EC, 0x0000000006C], // keyL - [0x000000007ED, 0x0000000006D], // keyM - [0x000000007EE, 0x0000000006E], // keyN - [0x000000007EF, 0x0000000006F], // keyO - [0x000000007F0, 0x00000000070], // keyP - [0x000000007F1, 0x00000000071], // keyQ - [0x000000007F2, 0x00000000072], // keyR - [0x000000007F3, 0x00000000073], // keyS - [0x000000007F4, 0x00000000074], // keyT - [0x000000007F5, 0x00000000075], // keyU - [0x000000007F6, 0x00000000076], // keyV - [0x000000007F7, 0x00000000077], // keyW - [0x000000007F8, 0x00000000078], // keyX - [0x000000007F9, 0x00000000079], // keyY - [0x000000007FA, 0x0000000007A], // keyZ - [0x000000007FB, 0x0000000002C], // comma - [0x000000007FC, 0x0000000002E], // period - [0x000000007FD, 0x00200000104], // altLeft - [0x000000007FE, 0x00200000105], // altRight - [0x000000007FF, 0x00200000102], // shiftLeft - [0x00000000800, 0x00200000103], // shiftRight - [0x00000000801, 0x00100000009], // tab - [0x00000000802, 0x00000000020], // space - [0x00000000804, 0x00100000B09], // launchWebBrowser - [0x00000000805, 0x00100000B03], // launchMail - [0x00000000806, 0x0010000000D], // enter - [0x00000000807, 0x00100000008], // backspace - [0x00000000808, 0x00000000060], // backquote - [0x00000000809, 0x0000000002D], // minus - [0x0000000080A, 0x0000000003D], // equal - [0x0000000080B, 0x0000000005B], // bracketLeft - [0x0000000080C, 0x0000000005D], // bracketRight - [0x0000000080D, 0x0000000005C], // backslash - [0x0000000080E, 0x0000000003B], // semicolon - [0x0000000080F, 0x00000000022], // apostrophe/quote - [0x00000000810, 0x0000000002F], // slash - [0x00000000813, 0x00100000505], // contextMenu - [0x000000009A2, 0x00100000704], // compose - [0x00000000814, 0x00100000308], // pageUp - [0x00000000815, 0x00100000307], // pageDown - [0x00000000816, 0x0010000001B], // escape - [0x00000000817, 0x0010000007F], // delete - [0x00000000818, 0x00200000100], // controlLeft - [0x00000000819, 0x00200000101], // controlRight - [0x0000000081A, 0x00100000104], // capsLock - [0x0000000081B, 0x0010000010C], // scrollLock - [0x0000000081C, 0x00200000106], // metaLeft - [0x0000000081D, 0x00200000107], // metaRight - [0x0000000081E, 0x00100000106], // fn - [0x0000000081F, 0x00100000608], // printScreen - [0x00000000820, 0x00100000509], // pause - [0x00000000821, 0x00100000306], // home - [0x00000000822, 0x00100000305], // end - [0x00000000823, 0x00100000407], // insert - [0x00000000824, 0x00100000C03], // browserForward - [0x00000000825, 0x00100000D2F], // mediaPlay - [0x00000000A53, 0x0010000050A], // play - [0x00000000826, 0x00100000D2E], // mediaPause - [0x00000000827, 0x00100000D5B], // mediaClose - [0x00000000828, 0x00100000604], // eject - [0x00000000829, 0x00100000D30], // mediaRecord - [0x0000000082A, 0x00100000801], // f1 - [0x0000000082B, 0x00100000802], // f2 - [0x0000000082C, 0x00100000803], // f3 - [0x0000000082D, 0x00100000804], // f4 - [0x0000000082E, 0x00100000805], // f5 - [0x0000000082F, 0x00100000806], // f6 - [0x00000000830, 0x00100000807], // f7 - [0x00000000831, 0x00100000808], // f8 - [0x00000000832, 0x00100000809], // f9 - [0x00000000833, 0x0010000080A], // f10 - [0x00000000834, 0x0010000080B], // f11 - [0x00000000835, 0x0010000080C], // f12 - [0x00000000836, 0x0010000010A], // numLock - [0x00000000837, 0x00200000230], // numpad0 - [0x00000000838, 0x00200000231], // numpad1 - [0x00000000839, 0x00200000232], // numpad2 - [0x0000000083A, 0x00200000233], // numpad3 - [0x0000000083B, 0x00200000234], // numpad4 - [0x0000000083C, 0x00200000235], // numpad5 - [0x0000000083D, 0x00200000236], // numpad6 - [0x0000000083E, 0x00200000237], // numpad7 - [0x0000000083F, 0x00200000238], // numpad8 - [0x00000000840, 0x00200000239], // numpad9 - [0x00000000841, 0x0020000022F], // numpadDivide - [0x00000000842, 0x0020000022A], // numpadMultiply - [0x00000000843, 0x0020000022D], // numpadSubtract - [0x00000000844, 0x0020000022B], // numpadAdd - [0x00000000845, 0x0020000022E], // numpadDecimal - [0x00000000846, 0x0020000022C], // numpadComma - [0x00000000847, 0x0020000020D], // numpadEnter - [0x00000000848, 0x0020000023D], // numpadEqual - [0x00000000849, 0x00200000228], // numpadParenLeft - [0x0000000084A, 0x00200000229], // numpadParenRight - [0x00000000010, 0x00100000A10], // audioVolumeUp - [0x00000000011, 0x00100000A0F], // audioVolumeDown - [0x00000000012, 0x00100000606], // power - [0x00000000016, 0x00100000E09], // microphoneVolumeMute - [0x00000000001, 0x00100000306], // home - [0x00000000002, 0x00100001005], // goBack - [0x00000000013, 0x00100000603], // camera - [0x00000000028, 0x00100000602], // brightnessUp - [0x00000000029, 0x00100000601], // brightnessDown - [0x00000000005, 0x00100000401], // clear - [0x0000000000A, 0x00100000A05], // mediaPlayPause - [0x0000000000B, 0x00100000A07], // mediaStop - [0x0000000000C, 0x00100000A08], // mediaTrackNext - [0x0000000000D, 0x00100000A09], // mediaTrackPrevious - [0x0000000000E, 0x00100000D31], // mediaRewind - [0x0000000000F, 0x00100000D2C], // mediaFastForward - [0x00000000A28, 0x00200000002], // sleep - [0x00000000A29, 0x0010000071D], // zenkakuHankaku - [0x00000000A2C, 0x0010000071A], // katakana - [0x00000000A2D, 0x00100000716], // hiragana - [0x00000000A2E, 0x00100000705], // convert - [0x00000000A2F, 0x00100000717], // hiraganaKatakana - [0x00000000A30, 0x0010000070D], // nonConvert - [0x00000000A37, 0x00200000022], // intlYen - [0x00000000A39, 0x00100000502], // again - [0x00000000A3A, 0x0010000050B], // props - [0x00000000A3B, 0x0010000040A], // undo - [0x00000000A3C, 0x00100000402], // copy - [0x00000000A3D, 0x00100000A0B], // open - [0x00000000A3E, 0x00100000408], // paste - [0x00000000A3F, 0x00100000507], // find - [0x00000000A40, 0x00100000404], // cut - [0x00000000A41, 0x00100000508], // help - [0x00000000A44, 0x00100000C02], // browserFavorites - [0x00000000A46, 0x00100000A05], // mediaPlayPause - [0x00000000A48, 0x00100000A01], // close - [0x00000000003, 0x00100001002], // call - [0x00000000A4B, 0x00100000C05], // browserRefresh - [0x00000000A4C, 0x00100000D15], // exit - [0x00000000A51, 0x00100000409], // redo - [0x00000000A52, 0x00100000A01], // close - [0x00000000A55, 0x00100000A0C], // print - [0x00000000A58, 0x00100000504], // cancel - [0x00000000A5F, 0x00100000A0D], // save - [0x00000000A68, 0x00100000D25], // info - [0x00000000A6B, 0x00100000D47], // subtitle - [0x00000000A70, 0x00100000D49], // tv - [0x00000000A7E, 0x00100000D0C], // colorF0Red - [0x00000000A7F, 0x00100000D0D], // colorF1Green - [0x00000000A80, 0x00100000D0E], // colorF2Yellow - [0x00000000A81, 0x00100000D0F], // colorF3Blue - [0x00000000A82, 0x00100000D0B], // channelUp - [0x00000000A83, 0x00100000D0A], // channelDown - [0x00000000A8A, 0x0010000050D], // zoomIn - [0x00000000A8B, 0x0010000050E], // zoomOut - [0x00000000A98, 0x00100000A0E], // spellCheck - [0x00000000AF2, 0x0010000060B], // wakeUp - [0x00000000AFD, 0x00100000604], // eject - [0x00000000B00, 0x0010000080D], // f13 - [0x00000000B01, 0x0010000080E], // f14 - [0x00000000B02, 0x0010000080F], // f15 - [0x00000000B03, 0x00100000810], // f16 - [0x00000000B04, 0x00100000811], // f17 - [0x00000000B05, 0x00100000812], // f18 - [0x00000000B06, 0x00100000813], // f19 - [0x00000000B07, 0x00100000814], // f20 - [0x00000000B08, 0x00100000815], // f21 - [0x00000000B09, 0x00100000816], // f22 - [0x00000000B0A, 0x00100000817], // f23 - [0x00000000B0B, 0x00100000818], // f24 - [0x00000000B0F, 0x00200000000], // suspend - [0x00000000B12, 0x0000000003F], // question - [0x00000000811, 0x00000000040], // at - [0x00000000006, 0x00100001007], // headsetHook - [0x00000000017, 0x00100000A11], // audioVolumeMute - [0x00000000004, 0x00100001004] // endCall - ]); - /** Map OH KeyCode to Flutter PhysicalKey. - * Should map OH ScanCode to Flutter PhysicalKey, but we use KeyCode here - * instead since there is no ScanCode in OH KeyEvent yet. There may be some - * mistakes and should correct the map as soon as we can access ScanCode. - */ - public static toPhysicalKey: Map = - new Map([ - [0x000000007D0, 0x00000070027], // digit0 - [0x000000007D1, 0x0000007001E], // digit1 - [0x000000007D2, 0x0000007001F], // digit2 - [0x000000007D3, 0x00000070020], // digit3 - [0x000000007D4, 0x00000070021], // digit4 - [0x000000007D5, 0x00000070022], // digit5 - [0x000000007D6, 0x00000070023], // digit6 - [0x000000007D7, 0x00000070024], // digit7 - [0x000000007D8, 0x00000070025], // digit8 - [0x000000007D9, 0x00000070026], // digit9 - [0x000000007DC, 0x00000070052], // arrowUp - [0x000000007DD, 0x00000070051], // arrowDown - [0x000000007DE, 0x00000070050], // arrowLeft - [0x000000007DF, 0x0000007004F], // arrowRight - [0x000000007E1, 0x00000070004], // keyA - [0x000000007E2, 0x00000070005], // keyB - [0x000000007E3, 0x00000070006], // keyC - [0x000000007E4, 0x00000070007], // keyD - [0x000000007E5, 0x00000070008], // keyE - [0x000000007E6, 0x00000070009], // keyF - [0x000000007E7, 0x0000007000A], // keyG - [0x000000007E8, 0x0000007000B], // keyH - [0x000000007E9, 0x0000007000C], // keyI - [0x000000007EA, 0x0000007000D], // keyJ - [0x000000007EB, 0x0000007000E], // keyK - [0x000000007EC, 0x0000007000F], // keyL - [0x000000007ED, 0x00000070010], // keyM - [0x000000007EE, 0x00000070011], // keyN - [0x000000007EF, 0x00000070012], // keyO - [0x000000007F0, 0x00000070013], // keyP - [0x000000007F1, 0x00000070014], // keyQ - [0x000000007F2, 0x00000070015], // keyR - [0x000000007F3, 0x00000070016], // keyS - [0x000000007F4, 0x00000070017], // keyT - [0x000000007F5, 0x00000070018], // keyU - [0x000000007F6, 0x00000070019], // keyV - [0x000000007F7, 0x0000007001A], // keyW - [0x000000007F8, 0x0000007001B], // keyX - [0x000000007F9, 0x0000007001C], // keyY - [0x000000007FA, 0x0000007001D], // keyZ - [0x000000007FB, 0x00000070036], // comma - [0x000000007FC, 0x00000070037], // period - [0x000000007FD, 0x000000700E2], // altLeft - [0x000000007FE, 0x000000700E6], // altRight - [0x000000007FF, 0x000000700E1], // shiftLeft - [0x00000000800, 0x000000700E5], // shiftRight - [0x00000000801, 0x0000007002B], // tab - [0x00000000802, 0x0000007002C], // space - [0x00000000805, 0x000000C018A], // launchMail - [0x00000000806, 0x00000070028], // enter - [0x00000000807, 0x0000007002A], // backspace - [0x00000000808, 0x00000070035], // backquote - [0x00000000809, 0x0000007002D], // minus - [0x0000000080A, 0x0000007002E], // equal - [0x0000000080B, 0x0000007002F], // bracketLeft - [0x0000000080C, 0x00000070030], // bracketRight - [0x0000000080D, 0x00000070031], // backslash - [0x0000000080E, 0x00000070033], // semicolon - [0x0000000080F, 0x00000070034], // apostrophe/quote - [0x00000000810, 0x00000070038], // slash - [0x00000000813, 0x00000070065], // contextMenu - [0x00000000814, 0x0000007004B], // pageUp - [0x00000000815, 0x0000007004E], // pageDown - [0x00000000816, 0x00000070029], // escape - [0x00000000817, 0x0000007004C], // delete - [0x00000000818, 0x000000700E0], // controlLeft - [0x00000000819, 0x000000700E4], // controlRight - [0x0000000081A, 0x00000070039], // capsLock - [0x0000000081B, 0x00000070047], // scrollLock - [0x0000000081C, 0x000000700E3], // metaLeft - [0x0000000081D, 0x000000700E7], // metaRight - [0x0000000081E, 0x00000000012], // fn - [0x0000000081F, 0x00000070046], // printScreen - [0x00000000820, 0x00000070048], // pause - [0x00000000821, 0x0000007004A], // home - [0x00000000822, 0x0000007004D], // end - [0x00000000823, 0x00000070049], // insert - [0x00000000824, 0x000000C0225], // browserForward - [0x00000000825, 0x000000C00B0], // mediaPlay - [0x00000000826, 0x000000C00B1], // mediaPause - [0x00000000828, 0x000000C00B8], // eject - [0x00000000829, 0x000000C00B2], // mediaRecord - [0x0000000082A, 0x0000007003A], // f1 - [0x0000000082B, 0x0000007003B], // f2 - [0x0000000082C, 0x0000007003C], // f3 - [0x0000000082D, 0x0000007003D], // f4 - [0x0000000082E, 0x0000007003E], // f5 - [0x0000000082F, 0x0000007003F], // f6 - [0x00000000830, 0x00000070040], // f7 - [0x00000000831, 0x00000070041], // f8 - [0x00000000832, 0x00000070042], // f9 - [0x00000000833, 0x00000070043], // f10 - [0x00000000834, 0x00000070044], // f11 - [0x00000000835, 0x00000070045], // f12 - [0x00000000836, 0x00000070053], // numLock - [0x00000000837, 0x00000070062], // numpad0 - [0x00000000838, 0x00000070059], // numpad1 - [0x00000000839, 0x0000007005A], // numpad2 - [0x0000000083A, 0x0000007005B], // numpad3 - [0x0000000083B, 0x0000007005C], // numpad4 - [0x0000000083C, 0x0000007005D], // numpad5 - [0x0000000083D, 0x0000007005E], // numpad6 - [0x0000000083E, 0x0000007005F], // numpad7 - [0x0000000083F, 0x00000070060], // numpad8 - [0x00000000840, 0x00000070061], // numpad9 - [0x00000000841, 0x00000070054], // numpadDivide - [0x00000000842, 0x00000070055], // numpadMultiply - [0x00000000843, 0x00000070056], // numpadSubtract - [0x00000000844, 0x00000070057], // numpadAdd - [0x00000000845, 0x00000070063], // numpadDecimal - [0x00000000846, 0x00000070085], // numpadComma - [0x00000000847, 0x00000070058], // numpadEnter - [0x00000000848, 0x00000070067], // numpadEqual - [0x00000000849, 0x000000700B6], // numpadParenLeft - [0x0000000084A, 0x000000700B7], // numpadParenRight - [0x00000000010, 0x00000070080], // audioVolumeUp - [0x00000000011, 0x00000070081], // audioVolumeDown - [0x00000000012, 0x00000070066], // power - [0x00000000001, 0x0000007004A], // home - [0x00000000028, 0x000000C006F], // brightnessUp - [0x00000000029, 0x000000C0070], // brightnessDown - [0x0000000000A, 0x000000C00CD], // mediaPlayPause - [0x0000000000B, 0x000000C00B7], // mediaStop - [0x0000000000C, 0x000000C00B5], // mediaTrackNext - [0x0000000000D, 0x000000C00B6], // mediaTrackPrevious - [0x0000000000E, 0x000000C00B4], // mediaRewind - [0x0000000000F, 0x000000C00B3], // mediaFastForward - [0x00000000A28, 0x00000010082], // sleep - [0x00000000A2E, 0x0000007008A], // convert - [0x00000000A30, 0x0000007008B], // nonConvert - [0x00000000A37, 0x00000070089], // intlYen - [0x00000000A39, 0x00000070079], // again - [0x00000000A3A, 0x000000700A3], // props - [0x00000000A3B, 0x0000007007A], // undo - [0x00000000A3C, 0x0000007007C], // copy - [0x00000000A3D, 0x00000070074], // open - [0x00000000A3E, 0x0000007007D], // paste - [0x00000000A3F, 0x0000007007E], // find - [0x00000000A40, 0x0000007007B], // cut - [0x00000000A41, 0x00000070075], // help - [0x00000000A44, 0x000000C022A], // browserFavorites - [0x00000000A46, 0x000000C00CD], // mediaPlayPause - [0x00000000A48, 0x000000C0203], // close - [0x00000000A4B, 0x000000C0227], // browserRefresh - [0x00000000A4C, 0x000000C0094], // exit - [0x00000000A51, 0x000000C0279], // redo - [0x00000000A52, 0x000000C0203], // close - [0x00000000A55, 0x000000C0208], // print - [0x00000000A5F, 0x000000C0207], // save - [0x00000000A68, 0x000000C0060], // info - [0x00000000A82, 0x000000C009C], // channelUp - [0x00000000A83, 0x000000C009D], // channelDown - [0x00000000A8A, 0x000000C022D], // zoomIn - [0x00000000A8B, 0x000000C022E], // zoomOut - [0x00000000A98, 0x000000C01AB], // spellCheck - [0x00000000AF2, 0x00000010083], // wakeUp - [0x00000000AFD, 0x000000C00B8], // eject - [0x00000000B00, 0x00000070068], // f13 - [0x00000000B01, 0x00000070069], // f14 - [0x00000000B02, 0x0000007006A], // f15 - [0x00000000B03, 0x0000007006B], // f16 - [0x00000000B04, 0x0000007006C], // f17 - [0x00000000B05, 0x0000007006D], // f18 - [0x00000000B06, 0x0000007006E], // f19 - [0x00000000B07, 0x0000007006F], // f20 - [0x00000000B08, 0x00000070070], // f21 - [0x00000000B09, 0x00000070071], // f22 - [0x00000000B0A, 0x00000070072], // f23 - [0x00000000B0B, 0x00000070073], // f24 - [0x00000000B0F, 0x00000000014], // suspend - [0x00000000017, 0x0000007007F] // audioVolumeMute - ]); - /** Map OpenHarmony KeyCode to ScanCode since cannot access ScanCode directly from KeyEvent. */ - public static ohKeyToScanCode: Map = - new Map([ - [0x000000007D0, 0x0000000000B], // digit0 - [0x000000007D1, 0x00000000002], // digit1 - [0x000000007D2, 0x00000000003], // digit2 - [0x000000007D3, 0x00000000004], // digit3 - [0x000000007D4, 0x00000000005], // digit4 - [0x000000007D5, 0x00000000006], // digit5 - [0x000000007D6, 0x00000000007], // digit6 - [0x000000007D7, 0x00000000008], // digit7 - [0x000000007D8, 0x00000000009], // digit8 - [0x000000007D9, 0x0000000000A], // digit9 - [0x000000007DC, 0x00000000067], // arrowUp - [0x000000007DD, 0x0000000006C], // arrowDown - [0x000000007DE, 0x00000000069], // arrowLeft - [0x000000007DF, 0x0000000006A], // arrowRight - [0x000000007E1, 0x0000000001E], // keyA - [0x000000007E2, 0x00000000030], // keyB - [0x000000007E3, 0x0000000002E], // keyC - [0x000000007E4, 0x00000000020], // keyD - [0x000000007E5, 0x00000000012], // keyE - [0x000000007E6, 0x00000000021], // keyF - [0x000000007E7, 0x00000000022], // keyG - [0x000000007E8, 0x00000000023], // keyH - [0x000000007E9, 0x00000000017], // keyI - [0x000000007EA, 0x00000000024], // keyJ - [0x000000007EB, 0x00000000025], // keyK - [0x000000007EC, 0x00000000026], // keyL - [0x000000007ED, 0x00000000032], // keyM - [0x000000007EE, 0x00000000031], // keyN - [0x000000007EF, 0x00000000018], // keyO - [0x000000007F0, 0x00000000019], // keyP - [0x000000007F1, 0x00000000010], // keyQ - [0x000000007F2, 0x00000000013], // keyR - [0x000000007F3, 0x0000000001F], // keyS - [0x000000007F4, 0x00000000014], // keyT - [0x000000007F5, 0x00000000016], // keyU - [0x000000007F6, 0x0000000002F], // keyV - [0x000000007F7, 0x00000000011], // keyW - [0x000000007F8, 0x0000000002D], // keyX - [0x000000007F9, 0x00000000015], // keyY - [0x000000007FA, 0x0000000002C], // keyZ - [0x000000007FB, 0x00000000033], // comma - [0x000000007FC, 0x00000000034], // period - [0x000000007FD, 0x00000000038], // altLeft - [0x000000007FE, 0x00000000064], // altRight - [0x000000007FF, 0x0000000002A], // shiftLeft - [0x00000000800, 0x00000000036], // shiftRight - [0x00000000801, 0x0000000000F], // tab - [0x00000000802, 0x00000000039], // space - [0x00000000805, 0x000000000D7], // launchMail - [0x00000000806, 0x0000000001C], // enter - [0x00000000807, 0x0000000000E], // backspace - [0x00000000808, 0x00000000029], // backquote - [0x00000000809, 0x0000000000C], // minus - [0x0000000080A, 0x0000000000D], // equal - [0x0000000080B, 0x0000000001A], // bracketLeft - [0x0000000080C, 0x0000000001B], // bracketRight - [0x0000000080D, 0x00000000056], // backslash - [0x0000000080E, 0x00000000027], // semicolon - [0x0000000080F, 0x00000000028], // apostrophe/quote - [0x00000000810, 0x00000000035], // slash - [0x00000000813, 0x0000000008B], // contextMenu - [0x00000000814, 0x000000000B1], // pageUp - [0x00000000815, 0x000000000B2], // pageDown - [0x00000000816, 0x00000000001], // escape - [0x00000000817, 0x0000000006F], // delete - [0x00000000818, 0x0000000001D], // controlLeft - [0x00000000819, 0x00000000061], // controlRight - [0x0000000081A, 0x0000000003A], // capsLock - [0x0000000081B, 0x00000000046], // scrollLock - [0x0000000081C, 0x0000000007D], // metaLeft - [0x0000000081D, 0x0000000007E], // metaRight - [0x0000000081E, 0x000000001D0], // fn - [0x0000000081F, 0x00000000063], // printScreen - [0x00000000820, 0x0000000019B], // pause - [0x00000000821, 0x00000000066], // home - [0x00000000822, 0x0000000006B], // end - [0x00000000823, 0x0000000006E], // insert - [0x00000000824, 0x0000000009F], // browserForward - [0x00000000825, 0x000000000CF], // mediaPlay - [0x00000000826, 0x000000000C9], // mediaPause - [0x00000000828, 0x000000000A2], // eject - [0x00000000829, 0x000000000A7], // mediaRecord - [0x0000000082A, 0x0000000003B], // f1 - [0x0000000082B, 0x0000000003C], // f2 - [0x0000000082C, 0x0000000003D], // f3 - [0x0000000082D, 0x0000000003E], // f4 - [0x0000000082E, 0x0000000003F], // f5 - [0x0000000082F, 0x00000000040], // f6 - [0x00000000830, 0x00000000041], // f7 - [0x00000000831, 0x00000000042], // f8 - [0x00000000832, 0x00000000043], // f9 - [0x00000000833, 0x00000000044], // f10 - [0x00000000834, 0x00000000057], // f11 - [0x00000000835, 0x00000000058], // f12 - [0x00000000836, 0x00000000045], // numLock - [0x00000000837, 0x00000000052], // numpad0 - [0x00000000838, 0x0000000004F], // numpad1 - [0x00000000839, 0x00000000050], // numpad2 - [0x0000000083A, 0x00000000051], // numpad3 - [0x0000000083B, 0x0000000004B], // numpad4 - [0x0000000083C, 0x0000000004C], // numpad5 - [0x0000000083D, 0x0000000004D], // numpad6 - [0x0000000083E, 0x00000000047], // numpad7 - [0x0000000083F, 0x00000000048], // numpad8 - [0x00000000840, 0x00000000049], // numpad9 - [0x00000000841, 0x00000000062], // numpadDivide - [0x00000000842, 0x00000000037], // numpadMultiply - [0x00000000843, 0x0000000004A], // numpadSubtract - [0x00000000844, 0x0000000004E], // numpadAdd - [0x00000000845, 0x00000000053], // numpadDecimal - [0x00000000846, 0x00000000079], // numpadComma - [0x00000000847, 0x00000000060], // numpadEnter - [0x00000000848, 0x00000000075], // numpadEqual - [0x00000000849, 0x000000000B3], // numpadParenLeft - [0x0000000084A, 0x000000000B4], // numpadParenRight - [0x00000000010, 0x00000000073], // audioVolumeUp - [0x00000000011, 0x00000000072], // audioVolumeDown - [0x00000000012, 0x00000000098], // power - [0x00000000001, 0x00000000066], // home - [0x00000000028, 0x000000000E1], // brightnessUp - [0x00000000029, 0x000000000E0], // brightnessDown - [0x0000000000A, 0x000000000A4], // mediaPlayPause - [0x0000000000B, 0x000000000A6], // mediaStop - [0x0000000000C, 0x000000000A3], // mediaTrackNext - [0x0000000000D, 0x000000000A5], // mediaTrackPrevious - [0x0000000000E, 0x000000000A8], // mediaRewind - [0x0000000000F, 0x000000000D0], // mediaFastForward - [0x00000000A28, 0x0000000008E], // sleep - [0x00000000A2E, 0x0000000005C], // convert - [0x00000000A30, 0x0000000005E], // nonConvert - [0x00000000A37, 0x0000000007C], // intlYen - [0x00000000A39, 0x00000000081], // again - [0x00000000A3A, 0x00000000082], // props - [0x00000000A3B, 0x00000000083], // undo - [0x00000000A3C, 0x00000000085], // copy - [0x00000000A3D, 0x00000000086], // open - [0x00000000A3E, 0x00000000087], // paste - [0x00000000A3F, 0x00000000088], // find - [0x00000000A40, 0x00000000089], // cut - [0x00000000A41, 0x0000000008A], // help - [0x00000000A44, 0x0000000009C], // browserFavorites - [0x00000000A46, 0x000000000A4], // mediaPlayPause - [0x00000000A48, 0x000000000CE], // close - [0x00000000A4C, 0x000000000AE], // exit - [0x00000000A51, 0x000000000B6], // redo - [0x00000000A52, 0x000000000CE], // close - [0x00000000A55, 0x000000000D2], // print - [0x00000000A68, 0x00000000166], // info - [0x00000000A82, 0x00000000192], // channelUp - [0x00000000A83, 0x00000000193], // channelDown - [0x00000000AF2, 0x0000000008F], // wakeUp - [0x00000000AFD, 0x000000000A2], // eject - [0x00000000B00, 0x000000000B7], // f13 - [0x00000000B01, 0x000000000B8], // f14 - [0x00000000B02, 0x000000000B9], // f15 - [0x00000000B03, 0x000000000BA], // f16 - [0x00000000B04, 0x000000000BB], // f17 - [0x00000000B05, 0x000000000BC], // f18 - [0x00000000B06, 0x000000000BD], // f19 - [0x00000000B07, 0x000000000BE], // f20 - [0x00000000B08, 0x000000000BF], // f21 - [0x00000000B09, 0x000000000C0], // f22 - [0x00000000B0A, 0x000000000C1], // f23 - [0x00000000B0B, 0x000000000C2], // f24 - [0x00000000B0F, 0x000000000CD], // suspend - [0x00000000017, 0x00000000071], // audioVolumeMute - ]); - /** Bitmask for extracting key values. */ - public static kValueMask: number = 0x000FFFFFFFF; - /** Unicode plane identifier. */ - public static kUnicodePlane: number = 0x00000000000; - /** OpenHarmony plane identifier. */ - public static kOhosPlane: number = 0x01900000000; - /** Array of modifier key goals for synchronization. */ - public static modifierGoals: ModifierGoal[] = [ - new ModifierGoal( - "Ctrl", - [ - new KeyPair(0x000700e0, 0x0200000100), // CtrlLeft - new KeyPair(0x000700e4, 0x0200000101) // CtrlRight - ] - ), - new ModifierGoal( - "Shift", - [ - new KeyPair(0x000700e1, 0x0200000102), // ShiftLeft - new KeyPair(0x000700e5, 0x0200000103) // SHIftRight - ] - ), - new ModifierGoal( - "Alt", - [ - new KeyPair(0x000700e2, 0x0200000104), // AltLeft - new KeyPair(0x000700e6, 0x0200000105) // AltRight - ] - ) - ]; -} - -// Due to the lack of ability of metaState in Ohos, and to maintain consistency -// in metaState with other platforms. Hence, flutter-ohos defines these modifier -// key meta values to check whether the following keys are pressed -export class ModifierKeyMetaInfo { - private constructor() {} - static readonly NONE: number = 0; // no modifier keys are pressed - static readonly ALT: number = 0x02; - static readonly ALT_LEFT: number = 0x10; - static readonly ALT_RIGHT: number = 0x20; - static readonly SHIFT: number = 0x01; - static readonly SHIFT_LEFT: number = 0x40; - static readonly SHIFT_RIGHT: number = 0x80; - static readonly CTRL: number = 0x1000; - static readonly CTRL_LEFT: number = 0x2000; - static readonly CTRL_RIGHT: number = 0x4000; - static readonly META: number = 0x10000; - static readonly META_LEFT: number = 0x20000; - static readonly META_RIGHT: number = 0x40000; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/OhosTouchProcessor.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/OhosTouchProcessor.ets deleted file mode 100644 index 5150568..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/OhosTouchProcessor.ets +++ /dev/null @@ -1,67 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import { TouchEvent } from '@ohos.multimodalInput.touchEvent'; -import Any from '../../plugin/common/Any'; - -/** - * Processor for handling touch events from OpenHarmony. - * This class processes touch events and converts them for Flutter. - */ -export default class OhosTouchProcessor { - private static POINTER_DATA_FIELD_COUNT: number = 35; - /** Number of bytes per field in pointer data. */ - static BYTES_PER_FIELD: number = 8; - private static POINTER_DATA_FLAG_BATCHED: number = 1; - - /** - * Processes a touch event. - * @param event - The touch event from OpenHarmony - * @param transformMatrix - The transformation matrix to apply - */ - public onTouchEvent(event: TouchEvent, transformMatrix: Any): void { - - } -} - -/** - * Types of pointer state changes. - */ -export enum PointerChange { - CANCEL = 0, - ADD = 1, - REMOVE = 2, - HOVER = 3, - DOWN = 4, - MOVE = 5, - UP = 6, - PAN_ZOOM_START = 7, - PAN_ZOOM_UPDATE = 8, - PAN_ZOOM_END = 9 -} - -/** - * Types of pointer input devices. - */ -export enum PointerDeviceKind { - TOUCH = 0, - MOUSE = 1, - STYLUS = 2, - INVERTED_STYLUS = 3, - TRACKPAD = 4, - UNKNOWN = 5 -} - -/** - * Types of pointer signal events. - */ -export enum PointerSignalKind { - NONE = 0, - SCROLL = 1, - SCROLL_INERTIA_CANCEL = 2, - SCALE = 3, - UNKNOWN = 4 -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/PlatformViewInfo.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/PlatformViewInfo.ets deleted file mode 100644 index 0aa90fa..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/PlatformViewInfo.ets +++ /dev/null @@ -1,49 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - - -import PlatformView from '../../plugin/platform/PlatformView'; - -/** - * Information about a platform view embedded in Flutter. - */ -export class PlatformViewInfo { - /** The platform view instance. */ - public platformView: PlatformView; - /** The surface ID for rendering. */ - public surfaceId: string; - /** The width of the platform view. */ - public width: number; - /** The height of the platform view. */ - public height: number; - /** The top position of the platform view. */ - public top: number; - /** The left position of the platform view. */ - public left: number; - /** The layout direction for the platform view. */ - public direction: Direction; - - /** - * Constructs a new PlatformViewInfo instance. - * @param platformView - The platform view instance - * @param surfaceId - The surface ID - * @param width - The width of the view - * @param height - The height of the view - * @param top - The top position - * @param left - The left position - * @param direction - The layout direction - */ - constructor(platformView: PlatformView, surfaceId: string, width: number, height: number, top: number, left: number, - direction: Direction) { - this.platformView = platformView; - this.surfaceId = surfaceId; - this.width = width; - this.height = height; - this.top = top; - this.left = left; - this.direction = direction; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/Settings.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/Settings.ets deleted file mode 100644 index 3633492..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/Settings.ets +++ /dev/null @@ -1,76 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import SettingsChannel, { PlatformBrightness } from '../engine/systemchannels/SettingsChannel' -import I18n from '@ohos.i18n' -import Log from '../../util/Log'; -import { MediaQuery } from '@ohos.arkui.UIContext'; - - -const TAG = "Settings"; - -/** - * Manages and sends system settings to Flutter. - * This class handles theme mode, text scale factor, and other system preferences. - */ -export default class Settings { - /** The SettingsChannel for sending settings to Flutter, or null if not set. */ - settingsChannel: SettingsChannel | null; - - /** - * Constructs a new Settings instance. - * @param settingsChannel - The SettingsChannel instance, or null - */ - constructor(settingsChannel: SettingsChannel | null) { - this.settingsChannel = settingsChannel; - } - - /** - * Sends current system settings to Flutter. - * @param mediaQuery - The MediaQuery instance for detecting theme mode - */ - sendSettings(mediaQuery: MediaQuery): void { - this.settingsChannel?.startMessage() - .setAlwaysUse24HourFormat(I18n.System.is24HourClock()) - .setNativeSpellCheckServiceDefined(false) - .setBrieflyShowPassword(false) - .setPlatformBrightness(this.getThemeMode(mediaQuery)) - .setTextScaleFactor(this.getTextScaleFactor()) - .send(); - } - - /** - * Gets the current theme mode (light or dark). - * @param mediaQuery - The MediaQuery instance for detecting dark mode - * @returns The platform brightness mode - */ - getThemeMode(mediaQuery: MediaQuery): PlatformBrightness { - - let listener = mediaQuery.matchMediaSync('(dark-mode: true)'); - if (listener.matches) { - Log.i(TAG, "return dark"); - return PlatformBrightness.DARK; - } else { - Log.i(TAG, "return light"); - return PlatformBrightness.LIGHT; - } - } - - /** - * Gets the text scale factor from system settings. - * @returns The text scale factor value - */ - getTextScaleFactor() : number { - let sysTextScaleFactor = AppStorage.get('fontSizeScale'); - if(sysTextScaleFactor == undefined) { - sysTextScaleFactor = 1.0; - Log.e(TAG, 'get textScaleFactor error, it is assigned to ' + JSON.stringify(sysTextScaleFactor)); - } - Log.i(TAG, "return textScaleFactor = " + JSON.stringify(sysTextScaleFactor)) - return sysTextScaleFactor; - } - -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/TouchEventProcessor.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/TouchEventProcessor.ets deleted file mode 100644 index e33ab28..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/TouchEventProcessor.ets +++ /dev/null @@ -1,539 +0,0 @@ -/* -* Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -/** Handle the motion events received by the FlutterNapi. */ -// import PlainArray from '@ohos.util.PlainArray'; -// import { TouchEvent } from '@ohos.multimodalInput.touchEvent'; -// import Queue from '@ohos.util.Queue'; - -import { CustomTouchEvent, CustomTouchObject } from '../../plugin/platform/CustomTouchEvent'; -import display from '@ohos.display'; -import FlutterManager from './FlutterManager'; -import { EmbeddingNodeController } from './EmbeddingNodeController'; -import Any from '../../plugin/common/Any'; - - -const OH_NATIVEXCOMPONENT_UNKNOWN = 4; -const OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN = 0; - -/** - * Represents a touch point in a native XComponent touch event. - */ -class OH_NativeXComponent_TouchPoint { - id: number = 0; - screenX: number = 0.0; - screenY: number = 0.0; - x: number = 0.0; - y: number = 0.0; - type: number = OH_NATIVEXCOMPONENT_UNKNOWN; - size: number = 0; - force: number = 0; - timeStamp: number = 0; - isPressed: boolean = false; - - /** - * Constructs a new OH_NativeXComponent_TouchPoint instance. - * @param id - The touch point ID - * @param screenX - The screen X coordinate - * @param screenY - The screen Y coordinate - * @param x - The local X coordinate - * @param y - The local Y coordinate - * @param type - The touch type - * @param size - The touch size - * @param force - The touch force - * @param timeStamp - The timestamp - * @param isPressed - Whether the touch is pressed - */ - constructor(id: number, - screenX: number, - screenY: number, - x: number, - y: number, - type: number, - size: number, - force: number, - timeStamp: number, - isPressed: boolean) { - this.id = id; - this.screenX = screenX; - this.screenY = screenY; - this.x = x; - this.y = y; - this.type = type; - this.size = size; - this.force = force; - this.timeStamp = timeStamp; - this.isPressed = isPressed; - } -} - -/** - * Represents a native XComponent touch event. - */ -class OH_NativeXComponent_TouchEvent { - id: number = 0; - screenX: number = 0.0; - screenY: number = 0.0; - x: number = 0.0; - y: number = 0.0; - type: number = OH_NATIVEXCOMPONENT_UNKNOWN; - size: number = 0; - force: number = 0; - deviceId: number = 0; - timeStamp: number = 0; - touchPoints: OH_NativeXComponent_TouchPoint[] = []; - numPoints: number = 0; - - /** - * Constructs a new OH_NativeXComponent_TouchEvent instance. - * @param id - The event ID - * @param screenX - The screen X coordinate - * @param screenY - The screen Y coordinate - * @param x - The local X coordinate - * @param y - The local Y coordinate - * @param type - The touch type - * @param size - The touch size - * @param force - The touch force - * @param deviceId - The device ID - * @param timeStamp - The timestamp - * @param touchPoints - Array of touch points - * @param numPoints - Number of touch points - */ - constructor(id: number, - screenX: number, - screenY: number, - x: number, - y: number, - type: number, - size: number, - force: number, - deviceId: number, - timeStamp: number, - touchPoints: OH_NativeXComponent_TouchPoint[], - numPoints: number) { - this.id = id; - this.screenX = screenX; - this.screenY = screenY; - this.x = x; - this.y = y; - this.type = type; - this.size = size; - this.force = force; - this.deviceId = deviceId; - this.timeStamp = timeStamp; - this.touchPoints = touchPoints; - this.numPoints = numPoints; - } -} - -/** - * Packet containing touch event data and tool information. - */ -class TouchPacket { - touchEvent: OH_NativeXComponent_TouchEvent; - toolType: number = OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN; - tiltX: number = 0; - tiltY: number = 0; - - /** - * Constructs a new TouchPacket instance. - * @param touchEvent - The touch event - * @param toolType - The tool type - * @param tiltX - The X-axis tilt - * @param tiltY - The Y-axis tilt - */ - constructor(touchEvent: OH_NativeXComponent_TouchEvent, - toolType: number, - tiltX: number, - tiltY: number) { - this.touchEvent = touchEvent; - this.toolType = toolType; - this.tiltX = tiltX; - this.tiltY = tiltY; - } -} - -/** - * Packet containing mouse event data. - */ -class MousePacket { - offset: number; - x: number; - y: number; - screenX: number; - screenY: number; - timestamp: number; - action: number; - button: number; - - /** - * Constructs a new MousePacket instance. - * @param offset - The offset value - * @param x - The local X coordinate - * @param y - The local Y coordinate - * @param screenX - The screen X coordinate - * @param screenY - The screen Y coordinate - * @param timestamp - The timestamp - * @param action - The mouse action - * @param button - The mouse button - */ - constructor( offset: number, - x: number, - y: number, - screenX: number, - screenY: number, - timestamp: number, - action: number, - button: number) { - this.offset = offset; - this.x = x; - this.y = y; - this.screenX = screenX; - this.screenY = screenY; - this.timestamp = timestamp; - this.action = action; - this.button = button; - } -} - -/** - * Processor for handling touch events received by FlutterNapi. - * This class processes and converts touch events from native format to Flutter format. - */ -export default class TouchEventProcessor { - private static instance: TouchEventProcessor; - - /** - * Gets the singleton instance of TouchEventProcessor. - * @returns The singleton TouchEventProcessor instance - */ - static getInstance(): TouchEventProcessor { - if (TouchEventProcessor.instance == null) { - TouchEventProcessor.instance = new TouchEventProcessor(); - } - return TouchEventProcessor.instance; - } - - private decodeTouchPacket(strings: Array, densityPixels: number, top: number, left: number): TouchPacket { - let offset: number = 0; - let numPoint: number = parseInt(strings[offset++]); - let changesId: number = parseInt(strings[offset++]); - let changesscreenX: number = (parseFloat(strings[offset++]) / densityPixels); - let changesscreenY: number = (parseFloat(strings[offset++]) / densityPixels); - let changesX: number = ((parseFloat(strings[offset++]) / densityPixels) - left); - let changesY: number = ((parseFloat(strings[offset++]) / densityPixels) - top); - let changesType: number = parseInt(strings[offset++]); - let changesSize: number = parseFloat(strings[offset++]); - let changesForce: number = parseFloat(strings[offset++]); - let changesDeviceId: number = parseInt(strings[offset++]); - let changesTimeStamp: number = parseInt(strings[offset++]); - - const touchPoints: OH_NativeXComponent_TouchPoint[] = []; - for (let i = 0; i < numPoint; i++) { - const touchPoint: OH_NativeXComponent_TouchPoint = new OH_NativeXComponent_TouchPoint( - parseInt(strings[offset++]), - (parseFloat(strings[offset++]) / densityPixels), - (parseFloat(strings[offset++]) / densityPixels), - ((parseFloat(strings[offset++]) / densityPixels) - left), - ((parseFloat(strings[offset++]) / densityPixels) - top), - parseInt(strings[offset++]), - parseFloat(strings[offset++]), - parseFloat(strings[offset++]), - parseInt(strings[offset++]), - parseInt(strings[offset++]) === 1 ? true : false - ); - touchPoints.push(touchPoint); - } - - const touchEventInput: OH_NativeXComponent_TouchEvent = new OH_NativeXComponent_TouchEvent( - changesId, - changesscreenX, - changesscreenY, - changesX, - changesY, - changesType, - changesSize, - changesForce, - changesDeviceId, - changesTimeStamp, - touchPoints, - numPoint - ); - - let toolTypeInput: number = parseInt(strings[offset++]); - let tiltXTouch: number = parseInt(strings[offset++]); - let tiltYTouch: number = parseInt(strings[offset++]); - - const touchPointEventPacket: TouchPacket = new TouchPacket( - touchEventInput, - toolTypeInput, - tiltXTouch, - tiltYTouch - ); - return touchPointEventPacket; - } - - private constructCustomTouchEventImpl(touchPacket: TouchPacket): CustomTouchEvent { - let changes1: CustomTouchObject = new CustomTouchObject( - touchPacket.touchEvent.type, - touchPacket.touchEvent.id, - touchPacket.touchEvent.screenX, - touchPacket.touchEvent.screenY, - touchPacket.touchEvent.screenX, - touchPacket.touchEvent.screenY, - touchPacket.touchEvent.screenX, - touchPacket.touchEvent.screenY, - touchPacket.touchEvent.x, - touchPacket.touchEvent.y - ); - - let touches: CustomTouchObject[] = []; - let touchPointer: number = touchPacket.touchEvent.numPoints; - for (let i = 0; i < touchPointer; i++) { - let touchesItem: CustomTouchObject = new CustomTouchObject( - touchPacket.touchEvent.touchPoints[i].type, - touchPacket.touchEvent.touchPoints[i].id, - touchPacket.touchEvent.touchPoints[i].screenX, - touchPacket.touchEvent.touchPoints[i].screenY, - touchPacket.touchEvent.touchPoints[i].screenX, - touchPacket.touchEvent.touchPoints[i].screenY, - touchPacket.touchEvent.touchPoints[i].screenX, - touchPacket.touchEvent.touchPoints[i].screenY, - touchPacket.touchEvent.touchPoints[i].x, - touchPacket.touchEvent.touchPoints[i].y - ); - touches.push(touchesItem); - } - - let customTouchEvent1: CustomTouchEvent = new CustomTouchEvent( - touchPacket.touchEvent.type, - touches, - [changes1], - touchPacket.touchEvent.timeStamp, - SourceType.TouchScreen, - touchPacket.touchEvent.force, - touchPacket.tiltX, - touchPacket.tiltY, - touchPacket.toolType - ); - - return customTouchEvent1; - } - - /** - * Constructs a CustomTouchEvent from string array data. - * @param strings - Array of string values representing touch data - * @param top - The top offset - * @param left - The left offset - * @returns A CustomTouchEvent instance - */ - public constructCustomTouchEvent(strings: Array, top: number, left: number): CustomTouchEvent { - let densityPixels: number = display.getDefaultDisplaySync().densityPixels; - - let touchPacket: TouchPacket = this.decodeTouchPacket(strings, densityPixels, top, left); - let customTouchEvent: CustomTouchEvent = this.constructCustomTouchEventImpl(touchPacket); - return customTouchEvent; - } - - /** - * Posts an axis/scroll event to platform views. - * Axis/wheel coordinates are passed to nodes through this method. - * @param strings - Array of string values representing axis event data - */ - public postAxisEvent(strings: Array) { - FlutterManager.getInstance().getFlutterViewList().forEach((value) => { - if (!value.getActive()) { - return - } - let length = value.getDVModel().children.length - for (let index = length - 1; index >= 0; index--) { - const dvModel = value.getDVModel().children[index] - const params = dvModel.getLayoutParams() as Record; - if (!params["hover"]) { - continue; - } - const left = params['left'] as number ?? 0; - const top = params['top'] as number ?? 0; - const densityPixels: number = display.getDefaultDisplaySync().densityPixels; - - let offset: number = 0; - const action: number = parseFloat(strings[offset++]); - const x: number = vp2px((parseInt(strings[offset++])) / densityPixels - left); - const y: number = vp2px((parseInt(strings[offset++])) / densityPixels - top); - const windowX: number = vp2px(parseFloat(strings[offset++]) / densityPixels); - const windowY: number = vp2px(parseFloat(strings[offset++]) / densityPixels); - const displayX: number = vp2px(parseFloat(strings[offset++]) / densityPixels); - const displayY: number = vp2px(parseFloat(strings[offset++]) / densityPixels); - const scroll: number = parseFloat(strings[offset++]); - - // 构造AxisEvent数据 - const axisEvent: AxisEvent = { - action: action, - x: x, - y: y, - windowX: x, - windowY: y, - displayX: displayX, - displayY: displayY, - scrollStep: scroll, - axisVertical: scroll - } as AxisEvent; - - let nodeController = params['nodeController'] as EmbeddingNodeController; - nodeController.postAxisEvent(axisEvent) - } - }); - } - - /** - * Posts a touch event to platform views. - * @param strings - Array of string values representing touch event data - */ - public postTouchEvent(strings: Array) { - FlutterManager.getInstance().getFlutterViewList().forEach((value) => { - if (!value.getActive()) { - return - } - let length = value.getDVModel().children.length - for (let index = length - 1; index >= 0; index--) { - let dvModel = value.getDVModel().children[index] - let params = dvModel.getLayoutParams() as Record; - let left = params['left'] as number ?? 0; - let top = params['top'] as number ?? 0; - let down = params['down'] as boolean ?? false; - if (down) { - //如果flutter端判断当前platformView是可点击的,则将事件分发出去 - let touchEvent: CustomTouchEvent = TouchEventProcessor.getInstance().constructCustomTouchEvent(strings, top, left); - let nodeController = params['nodeController'] as EmbeddingNodeController; - nodeController.postEvent(touchEvent) - } else { - //如果触摸事件为OH_NATIVEXCOMPONENT_DOWN=0,且只有一个手指,说明是下一次点击了,这时候需要清空上一次的数据 - if (strings[6] == '0' && strings[0] == '1') { - params['touchEvent'] = undefined - } - //如果触摸事件为OH_NATIVEXCOMPONENT_DOWN=0类型,且在flutter端还没判断当前view是否处于点击区域内,则 - //将点击事件存储在list列表中。 - let touchEvent: CustomTouchEvent = TouchEventProcessor.getInstance().constructCustomTouchEvent(strings, top, left); - let array: Array | undefined = params['touchEvent'] as Array - if (array == undefined) { - array = [] - params['touchEvent'] = array - } - array.push(touchEvent) - } - } - }); - } - - private decodeMousePacket(strings: Array, densityPixels: number, top: number, left: number): MousePacket { - let offset: number = 0; - let x: number = vp2px((parseInt(strings[offset++])) / densityPixels - left); - let y: number = vp2px((parseInt(strings[offset++])) / densityPixels - top); - let screenX: number = vp2px(parseFloat(strings[offset++]) / densityPixels); - let screenY: number = vp2px(parseFloat(strings[offset++]) / densityPixels); - let timestamp: number = parseFloat(strings[offset++]); - let action: number = parseFloat(strings[offset++]); - let button: number = parseFloat(strings[offset++]); - - const mouseEventPacket: MousePacket = new MousePacket( - offset, - x, - y, - screenX, - screenY, - timestamp, - action, - button - ); - return mouseEventPacket; - } - - private constructMouseEventImpl(mousePacket: MousePacket): MouseEvent { - // 构造MouseEvent数据 - const mouseEvent: MouseEvent = { - button: mousePacket.button, - action: mousePacket.action, - displayX: mousePacket.x, - displayY: mousePacket.y, - windowX: mousePacket.x, - windowY: mousePacket.y, - screenX: mousePacket.screenX, - screenY: mousePacket.screenY, - x: mousePacket.x, - y: mousePacket.y, - stopPropagation: () => {}, - timestamp: mousePacket.timestamp, - pressure: 0, - tiltX: mousePacket.x, - tiltY: mousePacket.y, - } as MouseEvent; - - return mouseEvent; - } - - /** Construct the MouseEvent and return. */ - public constructMouseEvent(strings: Array, top: number, left: number): MouseEvent { - let densityPixels: number = display.getDefaultDisplaySync().densityPixels; - - let mousePacket: MousePacket = this.decodeMousePacket(strings, densityPixels, top, left); - let mouseEvent: MouseEvent = this.constructMouseEventImpl(mousePacket); - return mouseEvent; - } - - // 鼠标坐标通过postInputEvent传入到节点 - /** - * Posts a mouse event to platform views. - * @param strings - Array of string values representing mouse event data - */ - public postMouseEvent(strings: Array) { - FlutterManager.getInstance().getFlutterViewList().forEach((value) => { - if (!value.getActive()) { - return - } - let length = value.getDVModel().children.length - for (let index = length - 1; index >= 0; index--) { - const dvModel = value.getDVModel().children[index] - const params = dvModel.getLayoutParams() as Record; - const left = params['left'] as number ?? 0; - const top = params['top'] as number ?? 0; - let down = params['down'] as boolean ?? false; - let mouseEvent: MouseEvent = TouchEventProcessor.getInstance().constructMouseEvent(strings, top, left); - - if (mouseEvent.action != MouseAction.Press && mouseEvent.action != MouseAction.Release && params["hover"]) { - // 如果鼠标事件不是OH_NATIVEXCOMPONENT_MOUSE_PRESS类型或OH_NATIVEXCOMPONENT_MOUSE_RELEASE类型,则 - // 直接分发出去 - let nodeController = params['nodeController'] as EmbeddingNodeController; - nodeController.postMouseEvent(mouseEvent) - } else if (down) { - // 如果flutter端判断当前platformView是可点击的,则将事件分发出去,并结束分发 - let nodeController = params['nodeController'] as EmbeddingNodeController; - nodeController.postMouseEvent(mouseEvent) - break; - } else { - // 如果鼠标事件为OH_NATIVEXCOMPONENT_MOUSE_PRESS,说明是下一次点击了,这时候需要清空上一次的数据 - if (strings[5] == '1') { - params['mouseEvent'] = undefined - } - // 如果鼠标事件为OH_NATIVEXCOMPONENT_MOUSE_PRESS类型,且在flutter端还没判断当前view是否处于点击区域内,则将点击事件存储在list列表中。 - let array: Array | undefined = params['mouseEvent'] as Array - if (array == undefined) { - array = [] - params['mouseEvent'] = array - } - array.push(mouseEvent) - } - } - }); - } - - public checkHitPlatformView(left: number, top: number, width: number, height: number, x: number, y: number): boolean { - if (x >= left && x <= (left + width) && y >= top && y <= (top + height)) { - return true; - } else { - return false; - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/TouchEventTracker.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/TouchEventTracker.ets deleted file mode 100644 index dfc30b0..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/TouchEventTracker.ets +++ /dev/null @@ -1,113 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -/** Tracks the motion events received by the FlutterView. */ -import PlainArray from '@ohos.util.PlainArray'; -import { TouchEvent } from '@ohos.multimodalInput.touchEvent'; -import Queue from '@ohos.util.Queue'; - -/** - * Tracks touch events and provides unique identifiers for them. - * This class maintains a singleton instance for managing touch event tracking. - */ -export class TouchEventTracker { - private eventById: PlainArray; - private unusedEvents: Queue; - private static INSTANCE: TouchEventTracker; - - /** - * Gets the singleton instance of TouchEventTracker. - * @returns The singleton TouchEventTracker instance - */ - public static getInstance(): TouchEventTracker { - if (TouchEventTracker.INSTANCE == null) { - TouchEventTracker.INSTANCE = new TouchEventTracker(); - } - return TouchEventTracker.INSTANCE; - } - - /** - * Constructs a new TouchEventTracker instance. - */ - constructor() { - this.eventById = new PlainArray(); - this.unusedEvents = new Queue(); - } - - /** - * Tracks the event and returns a unique TouchEventId identifying the event. - * @param event - The touch event to track - * @returns A unique TouchEventId for the event - */ - public track(event: TouchEvent): TouchEventId { - const eventId: TouchEventId = TouchEventId.createUnique(); - this.eventById.add(eventId.getId(), event); - this.unusedEvents.add(eventId.getId()); - return eventId; - } - - /** - * Returns the TouchEvent corresponding to the eventId while discarding all the motion events - * that occurred prior to the event represented by the eventId. - * @param eventId - The TouchEventId to retrieve - * @returns The TouchEvent corresponding to the eventId - */ - public pop(eventId: TouchEventId): TouchEvent { - // remove all the older events. - while (this.unusedEvents.length != 0 && this.unusedEvents.getFirst() < eventId.getId()) { - this.eventById.remove(this.unusedEvents.pop()); - } - - // remove the current event from the heap if it exists. - if (this.unusedEvents.length != 0 && this.unusedEvents.getFirst() == eventId.getId()) { - this.unusedEvents.pop(); - } - - const event: TouchEvent = this.eventById.get(eventId.getId()); - this.eventById.remove(eventId.getId()); - return event; - } -} - -/** - * Represents a unique identifier corresponding to a touch event. - */ -export class TouchEventId { - private static ID_COUNTER: number = 0; - private id: number; - - /** - * Constructs a new TouchEventId instance. - * @param id - The ID value - */ - constructor(id: number) { - this.id = id; - } - - /** - * Creates a TouchEventId from a given ID. - * @param id - The ID value - * @returns A new TouchEventId instance - */ - public static from(id: number): TouchEventId { - return new TouchEventId(id); - } - - /** - * Creates a unique TouchEventId with an auto-incremented ID. - * @returns A new unique TouchEventId instance - */ - public static createUnique(): TouchEventId { - return new TouchEventId(TouchEventId.ID_COUNTER++); - } - - /** - * Gets the ID value. - * @returns The ID value - */ - public getId(): number { - return this.id; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/WindowInfoRepositoryCallbackAdapterWrapper.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/WindowInfoRepositoryCallbackAdapterWrapper.ets deleted file mode 100644 index d262cef..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/WindowInfoRepositoryCallbackAdapterWrapper.ets +++ /dev/null @@ -1,18 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import UIAbility from '@ohos.app.ability.UIAbility'; - -/** - * Wrapper adapter for window information repository callbacks. - */ -export default class WindowInfoRepositoryCallbackAdapterWrapper { - /** - * Constructs a new WindowInfoRepositoryCallbackAdapterWrapper instance. - */ - constructor() { - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/PlatformPlugin.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/PlatformPlugin.ets deleted file mode 100644 index ce2719f..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/PlatformPlugin.ets +++ /dev/null @@ -1,509 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformPlugin.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; -import { BusinessError } from '@kit.BasicServicesKit'; -import PlatformChannel, { - AppSwitcherDescription, - Brightness, - ClipboardContentFormat, - HapticFeedbackType, - PlatformMessageHandler, - SoundType, - SystemChromeStyle, - SystemUiMode, - SystemUiOverlay -} from '../embedding/engine/systemchannels/PlatformChannel'; -import FlutterManager from '../embedding/ohos/FlutterManager'; -import pasteboard from '@ohos.pasteboard'; -import Log from '../util/Log'; -import vibrator from '@ohos.vibrator'; -import window from '@ohos.window'; -import common from '@ohos.app.ability.common'; -import { MethodResult } from './common/MethodChannel'; -import Any from './common/Any'; -import router from '@ohos.router'; -import { PasteboardUtils } from '../util/PasteboardUtils'; - -/** - * Plugin for handling platform-specific functionality in Flutter applications. - * This class manages system UI, clipboard, haptic feedback, and other platform services. - */ -export default class PlatformPlugin { - private static TAG = "PlatformPlugin"; - /** The callback handler for platform messages. */ - callback = new PlatformPluginCallback(); - - /** - * Constructs a new PlatformPlugin instance. - * @param platformChannel - The PlatformChannel for communication with Flutter. - * @param context - The application context. - * @param platformPluginDelegate - Optional delegate for platform-specific behavior. - */ - constructor(platformChannel: PlatformChannel, context: common.Context, - platformPluginDelegate?: PlatformPluginDelegate) { - this.callback.platformChannel = platformChannel; - this.callback.context = context; - this.callback.applicationContext = context?.getApplicationContext(); - this.callback.platform = this; - this.callback.platformPluginDelegate = platformPluginDelegate ?? null; - this.callback.platformChannel?.setPlatformMessageHandler(this.callback); - } - - /** - * Initializes the window references for system UI management. - */ - initWindow() { - try { - let context = this.callback.context!! - window.getLastWindow(context, (err, data) => { - if (err.code) { - Log.e(PlatformPlugin.TAG, "Failed to obtain the top window. Cause: " + JSON.stringify(err)); - return; - } - this.callback.lastWindow = data; - }); - const uiAbility = FlutterManager.getInstance().getUIAbility(context); - const windowStage = FlutterManager.getInstance().getWindowStage(uiAbility); - this.callback.mainWindow = windowStage.getMainWindowSync(); - } catch (err) { - Log.e(PlatformPlugin.TAG, "Failed to obtain the top window. Cause: " + JSON.stringify(err)); - } - } - - - /** - * Updates the system UI overlays (status bar and navigation bar) visibility. - */ - updateSystemUiOverlays(): void { - this.callback.mainWindow?.setWindowSystemBarEnable(this.callback.showBarOrNavigation); - if (this.callback.currentTheme != null) { - this.callback.setSystemChromeSystemUIOverlayStyle(this.callback.currentTheme); - } - } - - /** - * Sets the UIAbility context for platform operations. - * @param context - The UIAbility context - */ - setUIAbilityContext(context: common.UIAbilityContext): void { - this.callback.uiAbilityContext = context; - } - - /** - * Sets up a listener for system configuration changes. - */ - setSystemChromeChangeListener(): void { - if (this.callback.callbackId == null && this.callback.applicationContext != null) { - let that = this; - this.callback.callbackId = this.callback.applicationContext?.on('environment', { - onConfigurationUpdated(config) { - Log.d(PlatformPlugin.TAG, "onConfigurationUpdated: " + that.callback.showBarOrNavigation); - that.callback.platformChannel?.systemChromeChanged(that.callback.showBarOrNavigation.includes('status')); - }, - onMemoryLevel(level) { - } - }) - } - } - - /** - * Destroys the platform plugin and cleans up resources. - */ - public destroy() { - this.callback.platformChannel?.setPlatformMessageHandler(null); - } -} - -/** - * Delegate interface for platform-specific behavior. - */ -export interface PlatformPluginDelegate { - /** - * Called when the system navigator should be popped. - * @returns True if the delegate handled the pop, false otherwise. - */ - popSystemNavigator(): boolean; -} - -/** - * Callback implementation for handling platform messages from Flutter. - */ -export class PlatformPluginCallback implements PlatformMessageHandler { - private static TAG = "PlatformPluginCallback"; - /** The PlatformPlugin instance this callback is associated with. */ - platform: PlatformPlugin | null = null; - /** The main window for system UI operations. */ - mainWindow: window.Window | null = null; - /** The last window reference. */ - lastWindow: window.Window | null = null; - /** The PlatformChannel for communication with Flutter. */ - platformChannel: PlatformChannel | null = null; - /** The delegate for platform-specific behavior. */ - platformPluginDelegate: PlatformPluginDelegate | null = null; - /** The application context. */ - context: common.Context | null = null; - /** Array indicating which system bars should be shown ('status' and/or 'navigation'). */ - showBarOrNavigation: ('status' | 'navigation')[] = ['status', 'navigation']; - /** The UIAbility context for platform operations. */ - uiAbilityContext: common.UIAbilityContext | null = null; - /** The callback ID for system configuration changes. */ - callbackId: number | null = null; - /** The application context. */ - applicationContext: common.ApplicationContext | null = null; - /** The current system UI theme style. */ - currentTheme: SystemChromeStyle | null = null; - - /** - * Plays a system sound. - * @param soundType - The type of sound to play. - */ - playSystemSound(soundType: SoundType) { - } - - /** - * Triggers haptic feedback vibration. - * @param feedbackType - The type of haptic feedback - */ - async vibrateHapticFeedback(feedbackType: HapticFeedbackType) { - switch (feedbackType) { - case HapticFeedbackType.STANDARD: - await vibrator.startVibration({ type: 'time', duration: 75 }, - { id: 0, usage: 'touch' }); - break; - case HapticFeedbackType.LIGHT_IMPACT: - await vibrator.startVibration({ type: 'time', duration: 25 }, - { id: 0, usage: 'touch' }); - break; - case HapticFeedbackType.MEDIUM_IMPACT: - await vibrator.startVibration({ type: 'time', duration: 150 }, - { id: 0, usage: 'touch' }); - break; - case HapticFeedbackType.HEAVY_IMPACT: - await vibrator.startVibration({ type: 'time', duration: 300 }, - { id: 0, usage: 'touch' }); - break; - case HapticFeedbackType.SELECTION_CLICK: - await vibrator.startVibration({ type: 'time', duration: 100 }, - { id: 0, usage: 'touch' }); - break; - } - } - - /** - * Sets the preferred screen orientation. - * @param ohosOrientation - The orientation value. - * @param result - The method result callback. - */ - setPreferredOrientations(ohosOrientation: number, result: MethodResult) { - try { - Log.d(PlatformPluginCallback.TAG, "ohosOrientation: " + ohosOrientation); - this.mainWindow!.setPreferredOrientation(ohosOrientation, (err: BusinessError) => { - const errCode: number = err.code; - if (errCode) { - Log.e(PlatformPluginCallback.TAG, "Failed to set window orientation:" + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - return; - } - result.success(null); - }); - } catch (exception) { - Log.e(PlatformPluginCallback.TAG, "Failed to set window orientation:" + JSON.stringify(exception)); - result.error("error", JSON.stringify(exception), null); - } - } - - /** - * Sets the application switcher description (mission label). - * @param description - The app switcher description. - */ - setApplicationSwitcherDescription(description: AppSwitcherDescription) { - Log.d(PlatformPluginCallback.TAG, "setApplicationSwitcherDescription: " + JSON.stringify(description)); - try { - let label: string = description?.label; - this.uiAbilityContext?.setMissionLabel(label).then(() => { - Log.d(PlatformPluginCallback.TAG, "Succeeded in seting mission label"); - }) - } catch (err) { - Log.d(PlatformPluginCallback.TAG, "Failed to set mission label: " + JSON.stringify(err)); - } - } - - /** - * Shows the specified system UI overlays. - * @param overlays - Array of overlays to show. - */ - showSystemOverlays(overlays: SystemUiOverlay[]) { - this.setSystemChromeEnabledSystemUIOverlays(overlays); - } - - /** - * Sets the system UI mode. - * @param mode - The system UI mode to set. - */ - showSystemUiMode(mode: SystemUiMode) { - this.setSystemChromeEnabledSystemUIMode(mode); - } - - /** - * Sets up a listener for system UI changes. - */ - setSystemUiChangeListener() { - this.platform?.setSystemChromeChangeListener(); - } - - /** - * Restores the system UI overlays to their previous state. - */ - restoreSystemUiOverlays() { - this.platform?.updateSystemUiOverlays(); - } - - /** - * Sets the system UI overlay style (colors, brightness, etc.). - * @param systemUiOverlayStyle - The style to apply. - */ - setSystemUiOverlayStyle(systemUiOverlayStyle: SystemChromeStyle) { - Log.d(PlatformPluginCallback.TAG, "systemUiOverlayStyle:" + JSON.stringify(systemUiOverlayStyle)); - this.setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle); - } - - /** - * Pops the system navigator (goes back in navigation stack). - */ - popSystemNavigator() { - if (this.platformPluginDelegate != null && this.platformPluginDelegate?.popSystemNavigator()) { - return; - } - router.back(); - } - - /** - * Gets data from the system clipboard. - * @param result - The method result callback. - */ - getClipboardData(result: MethodResult): void { - let atManager = abilityAccessCtrl.createAtManager(); - atManager.requestPermissionsFromUser(this.uiAbilityContext, ['ohos.permission.READ_PASTEBOARD']).then((data) => { - // https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-permissionrequestresult-V5 - // Permission request result codes: - // -1: Not authorized, permission is configured but requires user to modify in Settings - // 0: Granted - // 2: Not authorized, request is invalid, possible reasons: - // - Target permission not declared in configuration file - // - Invalid permission name - // - Special conditions for certain permissions not met - enum AuthResultStatus { - NOT_CONFIGURED = -1, - GRANTED = 0, - INVALID_REQ = 2 - } - - let message: string = 'Failed to request permissions from user.'; - let authResult: number = data.authResults[0]; - switch (authResult) { - case AuthResultStatus.GRANTED: { - let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); - systemPasteboard.getData().then(async (pasteData: pasteboard.PasteData) => { - let pasteText: string = ''; - const recordCount: number = pasteData.getRecordCount(); - for (let i = 0; i < recordCount; i++) { - const record = pasteData.getRecord(i); - let text: string = ''; - if (typeof record.getValidTypes === 'function') { - // For api14 and above, click here. More formats are supported - text = await PasteboardUtils.getTargetTypesData(record); - } else if (record.mimeType === pasteboard.MIMETYPE_TEXT_HTML) { - const htmlText: StyledString = await StyledString.fromHtml(record.htmlText); - text = htmlText.getString(); - } else if (record.mimeType === pasteboard.MIMETYPE_TEXT_PLAIN) { - text = record.plainText; - } - pasteText += text; - } - let response: Any = new Map().set("text", pasteText); - result.success(response); - }).catch((err: BusinessError) => { - Log.e(PlatformPluginCallback.TAG, "Failed to get PasteData. Cause: " + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - }); - break; - } - case AuthResultStatus.NOT_CONFIGURED: { - message += 'Cause: Not configured in Settings'; - Log.i(PlatformPluginCallback.TAG, message); - result.success(null); - break; - } - case AuthResultStatus.INVALID_REQ: { - message += 'Cause: Invalid request'; - Log.i(PlatformPluginCallback.TAG, message); - result.success(null); - break; - } - default: { - message += `Unknown error: authResult=${authResult}`; - result.error("error", message, null); - break; - } - } - }).catch((err: BusinessError) => { - Log.e(PlatformPluginCallback.TAG, "Failed to request permissions from user. Cause: " + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - }) - } - - /** - * Sets data to the system clipboard. - * @param text - The text to set - * @param result - The method result callback - */ - setClipboardData(text: string, result: MethodResult) { - let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); - let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); - try { - systemPasteboard.setDataSync(pasteData); - result.success(null); - } catch (err) { - Log.d(PlatformPluginCallback.TAG, "Failed to set PasteData. Cause: " + JSON.stringify(err)); - result.error("error", JSON.stringify(err), null); - } - } - - /** - * Checks if the clipboard contains string data. - * @returns True if clipboard has strings, false otherwise - */ - clipboardHasStrings(): boolean { - return false; - } - - /** - * Sets the system UI mode (fullscreen, immersive, etc.). - * @param mode - The system UI mode to set - */ - setSystemChromeEnabledSystemUIMode(mode: SystemUiMode): void { - Log.d(PlatformPluginCallback.TAG, "mode: " + mode); - let uiConfig: ('status' | 'navigation')[] = []; - if (mode == SystemUiMode.LEAN_BACK) { - // Full screen mode, status and navigation bars can be shown by tapping anywhere on the display - FlutterManager.getInstance().setUseFullScreen(true, null); - } else if (mode == SystemUiMode.IMMERSIVE) { - // Full screen mode, status and navigation bars can be shown by swiping from display edges, gesture not received by app - FlutterManager.getInstance().setUseFullScreen(true, null); - } else if (mode == SystemUiMode.IMMERSIVE_STICKY) { - // Full screen mode, status and navigation bars can be shown by swiping from display edges, gesture received by app - FlutterManager.getInstance().setUseFullScreen(true, null); - } else if (mode == SystemUiMode.EDGE_TO_EDGE) { - uiConfig = ['status', 'navigation']; - } else { - return; - } - this.showBarOrNavigation = uiConfig; - this.platform?.updateSystemUiOverlays(); - } - - /** - * Sets the system UI overlay style with colors and brightness. - * @param systemChromeStyle - The style configuration - */ - setSystemChromeSystemUIOverlayStyle(systemChromeStyle: SystemChromeStyle): void { - let isStatusBarLightIconValue: boolean = false; - let statusBarContentColorValue: string | undefined = undefined; - let statusBarColorValue: string | undefined = undefined; - let navigationBarColorValue: string | undefined = undefined; - let isNavigationBarLightIconValue: boolean = false; - - const currentProps = this.mainWindow?.getWindowSystemBarProperties(); - - if (systemChromeStyle.statusBarIconBrightness != null) { - switch (systemChromeStyle.statusBarIconBrightness) { - case Brightness.DARK: - isStatusBarLightIconValue = false; - statusBarContentColorValue = '#000000'; - break; - case Brightness.LIGHT: - isStatusBarLightIconValue = true; - statusBarContentColorValue = '#FFFFFF'; - break; - } - } else { - isStatusBarLightIconValue = currentProps?.isStatusBarLightIcon ?? false - } - - if (systemChromeStyle.statusBarColor != null) { - statusBarColorValue = "#" + systemChromeStyle.statusBarColor.toString(16).padStart(8, '0'); - } else { - statusBarColorValue = currentProps?.statusBarColor - } - - if (systemChromeStyle.systemStatusBarContrastEnforced != null) { - - } - - if (systemChromeStyle.systemNavigationBarIconBrightness != null) { - switch (systemChromeStyle.systemNavigationBarIconBrightness) { - case Brightness.DARK: - isNavigationBarLightIconValue = true; - break; - case Brightness.LIGHT: - isNavigationBarLightIconValue = false; - } - } else { - isNavigationBarLightIconValue = currentProps?.isNavigationBarLightIcon ?? false - } - - if (systemChromeStyle.systemNavigationBarColor != null) { - navigationBarColorValue = "#" + systemChromeStyle.systemNavigationBarColor.toString(16).padStart(8, '0'); - } else { - navigationBarColorValue = currentProps?.navigationBarColor - } - - if (systemChromeStyle.systemNavigationBarContrastEnforced != null) { - - } - this.currentTheme = systemChromeStyle; - const systemBarProperties: window.SystemBarProperties = { - statusBarColor: statusBarColorValue, - isStatusBarLightIcon: isStatusBarLightIconValue, - statusBarContentColor: statusBarContentColorValue, - navigationBarColor: navigationBarColorValue, - isNavigationBarLightIcon: isNavigationBarLightIconValue, - navigationBarContentColor: currentProps?.navigationBarContentColor, - enableStatusBarAnimation: currentProps?.enableStatusBarAnimation, - enableNavigationBarAnimation: currentProps?.enableNavigationBarAnimation, - } - Log.d(PlatformPluginCallback.TAG, "systemBarProperties: " + JSON.stringify(systemBarProperties)); - this.mainWindow?.setWindowSystemBarProperties(systemBarProperties); - } - - /** - * Sets which system UI overlays should be enabled. - * @param overlays - Array of overlays to enable - */ - setSystemChromeEnabledSystemUIOverlays(overlays: SystemUiOverlay[]): void { - let uiConfig: ('status' | 'navigation')[] = []; - if (overlays.length == 0) { - - } - for (let index = 0; index < overlays.length; ++index) { - let overlayToShow = overlays[index]; - switch (overlayToShow) { - case SystemUiOverlay.TOP_OVERLAYS: - uiConfig.push('status'); //hide navigation - break; - case SystemUiOverlay.BOTTOM_OVERLAYS: - uiConfig.push('navigation'); //hide bar - break; - } - } - this.showBarOrNavigation = uiConfig; - this.platform?.updateSystemUiOverlays(); - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/Any.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/Any.ets deleted file mode 100644 index acc6e58..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/Any.ets +++ /dev/null @@ -1,9 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -declare type Any = ESObject; - -export default Any; \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BackgroundBasicMessageChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BackgroundBasicMessageChannel.ets deleted file mode 100644 index c818e9c..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BackgroundBasicMessageChannel.ets +++ /dev/null @@ -1,165 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import MessageChannelUtils from '../../util/MessageChannelUtils'; -import Log from '../../util/Log'; -import { BinaryReply } from './BinaryMessenger'; -import { TaskQueue } from './BinaryMessenger'; -import MessageCodec from './MessageCodec'; -import { BinaryMessenger } from './BinaryMessenger'; -import SendableBinaryMessageHandler from './SendableBinaryMessageHandler' -import SendableMessageCodec from './SendableMessageCodec'; -import SendableMessageHandler from './SendableMessageHandler'; -import StringUtils from '../../util/StringUtils'; - -/** - * A named channel for communicating with Flutter using basic, asynchronous message passing - * on background threads. This channel uses sendable codecs that can be passed across thread boundaries. - * - * Messages are encoded into binary before being sent, and binary messages received are decoded - * into objects. The {@link SendableMessageCodec} used must be compatible with the one used by the - * Flutter application. This can be achieved by creating a `BasicMessageChannel` counterpart of this channel - * on the Dart side. The type of messages sent and received - * is {@code Any}, but only values supported by the specified {@link SendableMessageCodec} can be used. - * - * The logical identity of the channel is given by its name. Identically named channels will - * interfere with each other's communication. - * @template T - The type of message being sent/received - */ -export default class BackgroundBasicMessageChannel { - /** Tag for logging. */ - public static TAG = "BackgroundBasicMessageChannel#"; - /** Channel name for buffer management. */ - public static CHANNEL_BUFFERS_CHANNEL = "dev.flutter/channel-buffers"; - private messenger: BinaryMessenger; - private name: string; - private codec: SendableMessageCodec; - private taskQueue: TaskQueue; - - /** - * Constructs a new BackgroundBasicMessageChannel instance. - * @param messenger - The BinaryMessenger to use for communication - * @param name - The channel name - * @param codec - The SendableMessageCodec to use for encoding/decoding messages - * @param taskQueue - Optional TaskQueue for background processing, defaults to a new background task queue - */ - constructor(messenger: BinaryMessenger, name: string, codec: SendableMessageCodec, taskQueue?: TaskQueue) { - this.messenger = messenger - this.name = name - this.codec = codec - this.taskQueue = taskQueue ?? messenger.makeBackgroundTaskQueue() - } - - /** - * Sends the specified message to the Flutter application, optionally expecting a reply. - * - * Any uncaught exception thrown by the reply callback will be caught and logged. - * - * @param message - The message, possibly null - * @param callback - A reply callback, possibly null - */ - send(message: T, callback?: (reply: T) => void): void { - this.messenger.send(this.name, this.codec.encodeMessage(message), - callback == null ? null : new IncomingReplyHandler(callback, this.codec)); - } - - /** - * Registers a message handler on this channel for receiving messages sent from the Flutter - * application. - * - * Overrides any existing handler registration for (the name of) this channel. - * - * If no handler has been registered, any incoming message on this channel will be handled - * silently by sending a null reply. - * - * @param handler - A {@link SendableMessageHandler}, or null to deregister - */ - setMessageHandler(handler: SendableMessageHandler | null): void { - this.messenger.setMessageHandler(this.name, - handler == null ? null : new IncomingSendableMessageHandler(handler, this.codec), this.taskQueue); - } - - /** - * Adjusts the number of messages that will get buffered when sending messages to channels that - * aren't fully set up yet. For example, the engine isn't running yet or the channel's message - * handler isn't set up on the Dart side yet. - * @param newSize - The new buffer size - */ - resizeChannelBuffer(newSize: number): void { - MessageChannelUtils.resizeChannelBuffer(this.messenger, this.name, newSize); - } -} - - -/** - * Internal handler for incoming replies from Flutter in background threads. - * @template T - The type of message being handled - */ -class IncomingReplyHandler implements BinaryReply { - private callback: (reply: T) => void; - private codec: SendableMessageCodec - - /** - * Constructs a new IncomingReplyHandler instance. - * @param callback - The callback to invoke with the decoded reply - * @param codec - The SendableMessageCodec to use for decoding - */ - constructor(callback: (reply: T) => void, codec: SendableMessageCodec) { - this.callback = callback - this.codec = codec - } - - /** - * Handles a binary reply from Flutter. - * @param reply - The binary reply, possibly null - */ - reply(reply: ArrayBuffer | null) { - try { - this.callback(this.codec.decodeMessage(reply)); - } catch (e) { - Log.e(BackgroundBasicMessageChannel.TAG, "Failed to handle message reply", e); - } - } -} - -/** - * Internal handler for incoming messages from Flutter in background threads. - * @template T - The type of message being handled - */ -@Sendable -class IncomingSendableMessageHandler implements SendableBinaryMessageHandler { - private handler: SendableMessageHandler - private codec: SendableMessageCodec - - /** - * Constructs a new IncomingSendableMessageHandler instance. - * @param handler - The SendableMessageHandler to delegate to - * @param codec - The SendableMessageCodec to use for encoding/decoding - */ - constructor(handler: SendableMessageHandler, codec: SendableMessageCodec) { - this.handler = handler; - this.codec = codec - } - - /** - * Handles a binary message from Flutter. - * @param message - The binary message - * @param callback - The BinaryReply callback to send a response - */ - onMessage(message: ArrayBuffer, callback: BinaryReply) { - try { - this.handler.onMessage( - this.codec.decodeMessage(message), - { - reply: (reply: T): void => { - callback.reply(this.codec.encodeMessage(reply)); - } - }); - } catch (e) { - Log.e('WARNNING', "Failed to handle message: ", e); - callback.reply(StringUtils.stringToArrayBuffer("")); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BackgroundMethodChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BackgroundMethodChannel.ets deleted file mode 100644 index 9a3913b..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BackgroundMethodChannel.ets +++ /dev/null @@ -1,185 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import Log from '../../util/Log'; -import MessageChannelUtils from '../../util/MessageChannelUtils'; -import StringUtils from '../../util/StringUtils'; -import { BinaryMessenger, BinaryReply, TaskQueue } from './BinaryMessenger'; -import Any from './Any'; -import MethodCall from './MethodCall'; -import MethodCodec from './MethodCodec'; -import { MethodResult } from './MethodChannel' -import SendableStandardMethodCodec from './SendableStandardMethodCodec'; -import SendableMethodCallHandler from './SendableMethodCallHandler' -import SendableMethodCodec from './SendableMethodCodec' -import SendableBinaryMessageHandler from './SendableBinaryMessageHandler' - -/** - * A named channel for communicating with Flutter using asynchronous method calls on background threads. - * This channel uses sendable codecs that can be passed across thread boundaries. - * - * Incoming method calls are decoded from binary on receipt, and results are encoded into - * binary before being transmitted back to Flutter. The {@link MethodCodec} used must be compatible - * with the one used by the Flutter application. This can be achieved by creating a `MethodChannel` - * counterpart of this channel on the Dart side. The type of method call arguments and results - * is {@code Any}, but only values supported by the specified {@link MethodCodec} can be used. - * - * The logical identity of the channel is given by its name. Identically named channels will - * interfere with each other's communication. - */ -export default class BackgroundMethodChannel { - /** Tag for logging. */ - static TAG = "BackgroundMethodChannel#"; - private messenger: BinaryMessenger; - private name: string; - private codec: SendableMethodCodec; - private taskQueue: TaskQueue; - private args: Object[]; - - /** - * Constructs a new BackgroundMethodChannel instance. - * @param messenger - The BinaryMessenger to use for communication - * @param name - The channel name - * @param codec - The SendableMethodCodec to use for encoding/decoding, defaults to SendableStandardMethodCodec.INSTANCE - * @param taskQueue - Optional TaskQueue for background processing, defaults to a new background task queue - * @param args - Additional arguments to pass to message handlers - */ - constructor(messenger: BinaryMessenger, - name: string, - codec: SendableMethodCodec = SendableStandardMethodCodec.INSTANCE, - taskQueue?: TaskQueue, - ...args: Object[]) { - this.messenger = messenger - this.name = name - this.codec = codec - this.taskQueue = taskQueue ?? messenger.makeBackgroundTaskQueue() - this.args = args - } - - /** - * Invokes a method on this channel, optionally expecting a result. - * - * Any uncaught exception thrown by the result callback will be caught and logged. - * - * @param method - The name of the method - * @param args - The arguments for the invocation, possibly null - * @param callback - A {@link MethodResult} callback for the invocation result, or null - */ - invokeMethod(method: string, args: Any, callback?: MethodResult): void { - this.messenger.send(this.name, - this.codec.encodeMethodCall(new MethodCall(method, args)), - callback == null ? null : new IncomingSendableResultHandler(callback, this.codec)); - } - - /** - * Registers a method call handler on this channel. - * - * Overrides any existing handler registration for (the name of) this channel. - * - * If no handler has been registered, any incoming method call on this channel will be handled - * silently by sending a null reply. This results in a `MissingPluginException` - * on the Dart side, unless an `OptionalMethodChannel` is used. - * - * @param handler - A {@link SendableMethodCallHandler}, or null to deregister - */ - setMethodCallHandler(handler: SendableMethodCallHandler | null): void { - this.messenger.setMessageHandler(this.name, - handler == null ? null : new IncomingSendableMethodCallHandler(handler, this.codec), - this.taskQueue, ...this.args); - } - - /** - * Adjusts the number of messages that will get buffered when sending messages to channels that - * aren't fully set up yet. For example, the engine isn't running yet or the channel's message - * handler isn't set up on the Dart side yet. - * @param newSize - The new buffer size - */ - resizeChannelBuffer(newSize: number): void { - MessageChannelUtils.resizeChannelBuffer(this.messenger, this.name, newSize); - } -} - -/** - * Internal handler for incoming method call results from Flutter in background threads. - */ -export class IncomingSendableResultHandler implements BinaryReply { - private callback: MethodResult; - private codec: SendableMethodCodec; - - /** - * Constructs a new IncomingSendableResultHandler instance. - * @param callback - The MethodResult callback to invoke - * @param codec - The SendableMethodCodec to use for decoding - */ - constructor(callback: MethodResult, codec: SendableMethodCodec) { - this.callback = callback; - this.codec = codec - } - - /** - * Handles a binary reply from Flutter. - * @param reply - The binary reply, possibly null - */ - reply(reply: ArrayBuffer | null): void { - try { - if (reply == null) { - this.callback.notImplemented(); - } else { - try { - this.callback.success(this.codec.decodeEnvelope(reply)); - } catch (e) { - this.callback.error(e.code, e.getMessage(), e.details); - } - } - } catch (e) { - Log.e(BackgroundMethodChannel.TAG, "Failed to handle method call result", e); - } - } -} - -/** - * Internal handler for incoming method calls from Flutter in background threads. - */ -@Sendable -export class IncomingSendableMethodCallHandler implements SendableBinaryMessageHandler { - private handler: SendableMethodCallHandler; - private codec: SendableMethodCodec; - - /** - * Constructs a new IncomingSendableMethodCallHandler instance. - * @param handler - The SendableMethodCallHandler to delegate to - * @param codec - The SendableMethodCodec to use for encoding/decoding - */ - constructor(handler: SendableMethodCallHandler, codec: SendableMethodCodec) { - this.handler = handler; - this.codec = codec; - } - - /** - * Handles a binary method call from Flutter. - * @param message - The binary message containing the method call - * @param reply - The BinaryReply callback to send a response - * @param args - Additional arguments passed to the handler - */ - onMessage(message: ArrayBuffer, reply: BinaryReply, ...args: Object[]): void { - try { - this.handler.onMethodCall( - this.codec.decodeMethodCall(message), - { - success: (result: Any): void => { - reply.reply(this.codec.encodeSuccessEnvelope(result)); - }, - error: (errorCode: string, errorMessage: string, errorDetails: Any): void => { - reply.reply(this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails)); - }, - notImplemented: (): void => { - reply.reply(StringUtils.stringToArrayBuffer("")); - } - }, ...args); - } catch (e) { - reply.reply(this.codec.encodeErrorEnvelopeWithStacktrace("error", e.getMessage(), null, e)); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BasicMessageChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BasicMessageChannel.ets deleted file mode 100644 index ca7057b..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BasicMessageChannel.ets +++ /dev/null @@ -1,200 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on BasicMessageChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import MessageChannelUtils from '../../util/MessageChannelUtils'; -import { BinaryMessageHandler } from './BinaryMessenger'; -import Log from '../../util/Log'; -import { BinaryReply } from './BinaryMessenger'; -import { TaskQueue } from './BinaryMessenger'; -import MessageCodec from './MessageCodec'; -import { BinaryMessenger } from './BinaryMessenger'; -import StringUtils from '../../util/StringUtils'; - -/** - * A named channel for communicating with the Flutter application using basic, asynchronous message - * passing. - * - * Messages are encoded into binary before being sent, and binary messages received are decoded - * into objects. The {@link MessageCodec} used must be compatible with the one used by the - * Flutter application. This can be achieved by creating a `BasicMessageChannel` - * counterpart of this channel on the Dart side. The static type of messages sent and received - * is `Object`, but only values supported by the specified {@link MessageCodec} can be used. - * - * The logical identity of the channel is given by its name. Identically named channels will - * interfere with each other's communication. - */ -export default class BasicMessageChannel { - /** Tag for logging. */ - public static TAG = "BasicMessageChannel#"; - /** Channel name for buffer management. */ - public static CHANNEL_BUFFERS_CHANNEL = "dev.flutter/channel-buffers"; - private messenger: BinaryMessenger; - private name: string; - private codec: MessageCodec; - - /** - * Constructs a new BasicMessageChannel instance. - * @param messenger - The BinaryMessenger to use for communication - * @param name - The channel name - * @param codec - The MessageCodec to use for encoding/decoding messages - */ - constructor(messenger: BinaryMessenger, name: string, codec: MessageCodec) { - this.messenger = messenger - this.name = name - this.codec = codec - } - - /** - * Sends the specified message to the Flutter application, optionally expecting a reply. - * - * Any uncaught exception thrown by the reply callback will be caught and logged. - * - * @param message - The message, possibly null - * @param callback - A {@link Reply} callback, possibly null - */ - send(message: T, callback?: (reply: T) => void): void { - this.messenger.send(this.name, this.codec.encodeMessage(message), - callback == null ? null : new IncomingReplyHandler(callback, this.codec)); - } - - /** - * Registers a message handler on this channel for receiving messages sent from the Flutter - * application. - * - * Overrides any existing handler registration for (the name of) this channel. - * - * If no handler has been registered, any incoming message on this channel will be handled - * silently by sending a null reply. - * - * @param handler - A {@link MessageHandler}, or null to deregister - */ - setMessageHandler(handler: MessageHandler | null): void { - this.messenger.setMessageHandler(this.name, - handler == null ? null : new IncomingMessageHandler(handler, this.codec)); - } - - /** - * Adjusts the number of messages that will get buffered when sending messages to channels that - * aren't fully set up yet. For example, the engine isn't running yet or the channel's message - * handler isn't set up on the Dart side yet. - * @param newSize - The new buffer size - */ - resizeChannelBuffer(newSize: number): void { - MessageChannelUtils.resizeChannelBuffer(this.messenger, this.name, newSize); - } -} - -/** - * Interface for handling message replies in BasicMessageChannel. - * @template T - The type of message being replied to - */ -export interface Reply { - /** - * Handles the specified message reply. - * - * @param reply - The reply, possibly null - */ - reply: (reply: T) => void; -} - -/** - * Interface for handling incoming messages in BasicMessageChannel. - * @template T - The type of message being handled - */ -export interface MessageHandler { - - /** - * Handles the specified message received from Flutter. - * - * Handler implementations must reply to all incoming messages, by submitting a single reply - * message to the given {@link Reply}. Failure to do so will result in lingering Flutter reply - * handlers. The reply may be submitted asynchronously and invoked on any thread. - * - * Any uncaught exception thrown by this method, or the preceding message decoding, will be - * caught by the channel implementation and logged, and a null reply message will be sent back - * to Flutter. - * - * Any uncaught exception thrown during encoding a reply message submitted to the {@link Reply} - * is treated similarly: the exception is logged, and a null reply is sent to Flutter. - * - * @param message - The message, possibly null - * @param reply - A {@link Reply} for sending a single message reply back to Flutter - */ - onMessage(message: T, reply: Reply): void; -} - -/** - * Internal handler for incoming replies from Flutter. - * @template T - The type of message being handled - */ -class IncomingReplyHandler implements BinaryReply { - private callback: (reply: T) => void; - private codec: MessageCodec - - /** - * Constructs a new IncomingReplyHandler instance. - * @param callback - The callback to invoke with the decoded reply - * @param codec - The MessageCodec to use for decoding - */ - constructor(callback: (reply: T) => void, codec: MessageCodec) { - this.callback = callback - this.codec = codec - } - - /** - * Handles a binary reply from Flutter. - * @param reply - The binary reply, possibly null - */ - reply(reply: ArrayBuffer | null) { - try { - this.callback(this.codec.decodeMessage(reply)); - } catch (e) { - Log.e(BasicMessageChannel.TAG, "Failed to handle message reply", e); - } - } -} - -/** - * Internal handler for incoming messages from Flutter. - * @template T - The type of message being handled - */ -class IncomingMessageHandler implements BinaryMessageHandler { - private handler: MessageHandler - private codec: MessageCodec - - /** - * Constructs a new IncomingMessageHandler instance. - * @param handler - The MessageHandler to delegate to - * @param codec - The MessageCodec to use for encoding/decoding - */ - constructor(handler: MessageHandler, codec: MessageCodec) { - this.handler = handler; - this.codec = codec - } - - /** - * Handles a binary message from Flutter. - * @param message - The binary message - * @param callback - The BinaryReply callback to send a response - */ - onMessage(message: ArrayBuffer, callback: BinaryReply) { - try { - this.handler.onMessage( - this.codec.decodeMessage(message), - { - reply: (reply: T): void => { - callback.reply(this.codec.encodeMessage(reply)); - } - }); - } catch (e) { - Log.e(BasicMessageChannel.TAG, "Failed to handle message", e); - callback.reply(StringUtils.stringToArrayBuffer("")); - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryCodec.ets deleted file mode 100644 index bf64447..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryCodec.ets +++ /dev/null @@ -1,58 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on BinaryCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import MessageCodec from './MessageCodec'; - -/** - * A {@link MessageCodec} using unencoded binary messages, represented as {@link ArrayBuffer}s. - * - * This codec is guaranteed to be compatible with the corresponding `BinaryCodec` on the Dart side. - * These parts of the Flutter SDK are evolved synchronously. - * - * On the Dart side, messages are represented using {@code ByteData}. - */ - -export default class BinaryCodec implements MessageCodec { - private returnsDirectByteBufferFromDecoding: boolean = false; - /** Direct instance that returns the direct buffer from decoding. */ - static readonly INSTANCE_DIRECT = new BinaryCodec(true); - - /** - * Constructs a new BinaryCodec instance. - * @param returnsDirectByteBufferFromDecoding - Whether to return the direct buffer from decoding - */ - constructor(returnsDirectByteBufferFromDecoding: boolean) { - this.returnsDirectByteBufferFromDecoding = returnsDirectByteBufferFromDecoding; - } - - /** - * Encodes a binary message (no-op for binary codec). - * @param message - The ArrayBuffer message to encode - * @returns The same ArrayBuffer - */ - encodeMessage(message: ArrayBuffer): ArrayBuffer { - return message - } - - /** - * Decodes a binary message. - * @param message - The ArrayBuffer message to decode, possibly null - * @returns The decoded ArrayBuffer, or a copy depending on configuration - */ - decodeMessage(message: ArrayBuffer | null): ArrayBuffer { - if (message == null) { - return new ArrayBuffer(0); - } else if (this.returnsDirectByteBufferFromDecoding) { - return message; - } else { - return message.slice(0, message.byteLength); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger.ets deleted file mode 100644 index ccdade6..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger.ets +++ /dev/null @@ -1,197 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on BinaryMessenger.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * An abstraction over the threading policy used to invoke message handlers. - * - * These are generated by calling methods like {@link BinaryMessenger#makeBackgroundTaskQueue(TaskQueueOptions)} and can - * be passed into platform channels' constructors to control the threading policy for handling platform channels' messages. - */ - -import SendableBinaryMessageHandler from './SendableBinaryMessageHandler' - -/** - * An abstraction over the threading policy used to invoke message handlers. - * Task queues are used to control which thread handles platform channel messages. - */ -export interface TaskQueue {} - -/** - * The priority of task execution - * - * This priority is guaranteed to be compatible with `taskpool` - * ({@link https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-taskpool-V5#priority}). - * - */ -export enum TaskPriority { - HIGH = 0, - MEDIUM = 1, - LOW = 2, - IDLE = 3 -} - -/** - * Options that control how a TaskQueue should operate and be created. - */ -export class TaskQueueOptions { - private isSerial: boolean = true; - private isSingleThread: boolean = false; - private priority: TaskPriority = TaskPriority.MEDIUM; - - /** - * Gets whether tasks should be executed serially. - * @returns True if tasks are executed serially, false otherwise - */ - getIsSerial():boolean { - return this.isSerial; - } - - /** - * Sets whether tasks should be executed serially. - * @param isSerial - True to execute tasks serially, false for concurrent execution - * @returns This TaskQueueOptions instance for method chaining - */ - setIsSerial(isSerial: boolean): TaskQueueOptions { - this.isSerial = isSerial; - return this; - } - - /** - * Gets the task priority. - * @returns The current task priority - */ - getPriority(): TaskPriority { - return this.priority; - } - - /** - * Sets the task priority. - * @param priority - The task priority to set - * @returns This TaskQueueOptions instance for method chaining - */ - setPriority(priority: TaskPriority): TaskQueueOptions { - this.priority = priority; - return this; - } - - /** - * Checks if single thread mode is enabled. - * @returns True if single thread mode is enabled, false otherwise - */ - isSingleThreadMode(): boolean { - return this.isSingleThread; - } - - /** - * Sets single thread mode. - * @param isSingleThread - True to enable single thread mode, false otherwise - * @returns This TaskQueueOptions instance for method chaining - */ - setSingleThreadMode(isSingleThread: boolean): TaskQueueOptions { - this.isSingleThread = isSingleThread; - return this; - } -} - -/** - * Binary message reply callback. Used to submit a reply to an incoming message from Flutter. Also - * used in the dual capacity to handle a reply received from Flutter after sending a message. - */ -export interface BinaryReply { - /** - * Handles the specified reply. - * - * @param reply - The reply payload, an {@link ArrayBuffer} or null. Senders of - * outgoing replies must place the reply bytes in the buffer. - * Reply receivers can read from the buffer directly. - */ - reply: (reply: ArrayBuffer | null) => void; -} - -/** Handler for incoming binary messages from Flutter. */ -export interface BinaryMessageHandler { - /** - * Handles the specified message. - * - * Handler implementations must reply to all incoming messages, by submitting a single reply - * message to the given {@link BinaryReply}. Failure to do so will result in lingering Flutter - * reply handlers. The reply may be submitted asynchronously. - * - * Any uncaught exception thrown by this method will be caught by the messenger - * implementation and logged, and a null reply message will be sent back to Flutter. - * - * @param message - The message {@link ArrayBuffer} payload, possibly null - * @param reply - A {@link BinaryReply} used for submitting a reply back to Flutter - */ - onMessage(message: ArrayBuffer, reply: BinaryReply): void; -} - -/** - * Facility for communicating with Flutter using asynchronous message passing with binary messages. - * The Flutter Dart code should use `BinaryMessages` to participate. - * - * BinaryMessenger is expected to be utilized from a single thread throughout the - * duration of its existence. If created on the main thread, then all invocations should take place - * on the main thread. If created on a background thread, then all invocations should take place on - * that background thread. - * - * @see BasicMessageChannel , which supports message passing with Strings and semi-structured - * messages. - * @see MethodChannel , which supports communication using asynchronous method invocation. - * @see EventChannel , which supports communication using event streams. - */ - -export interface BinaryMessenger { - /** - * Creates a background task queue for handling messages on background threads. - * @param options - Optional TaskQueueOptions to configure the task queue - * @returns A TaskQueue instance - */ - makeBackgroundTaskQueue(options?: TaskQueueOptions): TaskQueue; - - /** - * Sends a binary message to the Flutter application. - * - * @param channel - The name of the logical channel used for the message - * @param message - The message payload, an {@link ArrayBuffer} or null - */ - send(channel: String, message: ArrayBuffer | null): void; - - /** - * Sends a binary message to the Flutter application, optionally expecting a reply. - * - * Any uncaught exception thrown by the reply callback will be caught and logged. - * - * @param channel - The name of the logical channel used for the message - * @param message - The message payload, an {@link ArrayBuffer} or null - * @param callback - A {@link BinaryReply} callback invoked when the Flutter application responds to - * the message, possibly null - */ - send(channel: String, message: ArrayBuffer, callback?: BinaryReply | null): void; - - /** - * Registers a handler to be invoked when the Flutter application sends a message to its host - * platform. - * - * Registration overwrites any previous registration for the same channel name. Use a null - * handler to deregister. - * - * If no handler has been registered for a particular channel, any incoming message on that - * channel will be handled silently by sending a null reply. - * - * @param channel - The name of the channel - * @param handler - A {@link BinaryMessageHandler} to be invoked on incoming messages, or null - * @param taskQueue - A {@link BinaryMessenger.TaskQueue} that specifies what thread will execute - * the handler. Specifying null means execute on the platform thread - * @param args - Additional arguments - */ - setMessageHandler(channel: String, handler: BinaryMessageHandler | SendableBinaryMessageHandler | null, - taskQueue?: TaskQueue, ...args: Object[]): void; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/EventChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/EventChannel.ets deleted file mode 100644 index e736f8f..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/EventChannel.ets +++ /dev/null @@ -1,334 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on EventChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Log from '../../util/Log'; -import { BinaryMessageHandler, BinaryMessenger, BinaryReply, TaskQueue } from './BinaryMessenger'; -import Any from './Any'; -import MethodCodec from './MethodCodec'; -import StandardMethodCodec from './StandardMethodCodec'; - -const TAG = "EventChannel#"; - -/** - * A named channel for communicating with the Flutter application using asynchronous event streams. - * - * Incoming requests for event stream setup are decoded from binary on receipt, and - * responses and events are encoded into binary before being transmitted back to Flutter. The {@link MethodCodec} - * used must be compatible with the one used by the Flutter application. This can be achieved by creating an - * `EventChannel` counterpart of this channel on the Dart side. The type of stream configuration arguments, events, - * and error details is {@code Any}, but only values supported by the specified {@link MethodCodec} can be used. - * - * The logical identity of the channel is given by its name. Identically named channels will - * interfere with each other's communication. - */ -export default class EventChannel { - private messenger: BinaryMessenger; - private name: string; - private codec: MethodCodec; - private taskQueue: TaskQueue | null; - - /** - * Constructs a new EventChannel instance. - * @param messenger - The BinaryMessenger to use for communication - * @param name - The channel name - * @param codec - The MethodCodec to use for encoding/decoding, defaults to StandardMethodCodec.INSTANCE - * @param taskQueue - Optional TaskQueue for background processing - */ - constructor(messenger: BinaryMessenger, name: string, codec?: MethodCodec, taskQueue?: TaskQueue) { - this.messenger = messenger - this.name = name - this.codec = codec ? codec : StandardMethodCodec.INSTANCE - // TODO:(0xZOne): 实现后台处理 - // this.taskQueue = taskQueue ?? null - this.taskQueue = null - } - - - /** - * Registers a stream handler on this channel. - * - * Overrides any existing handler registration for (the name of) this channel. - * - * If no handler has been registered, any incoming stream setup requests will be handled - * silently by providing an empty stream. - * - * @param handler - A {@link StreamHandler}, or null to deregister - */ - setStreamHandler(handler: StreamHandler): void { - // We call the 2 parameter variant specifically to avoid breaking changes in - // mock verify calls. - // See https://github.com/flutter/flutter/issues/92582. - if (this.taskQueue != null) { - this.messenger.setMessageHandler( - this.name, - handler == null ? null : new IncomingStreamRequestHandler(handler, this.name, this.codec, this.messenger), - this.taskQueue); - } else { - this.messenger.setMessageHandler( - this.name, - handler == null ? null : new IncomingStreamRequestHandler(handler, this.name, this.codec, this.messenger)); - } - } -} - -/** - * Handler of stream setup and teardown requests. - * - * Implementations must be prepared to accept sequences of alternating calls to onListen and onCancel. - * Implementations should ideally consume no resources when the last such call is not onListen. - * In typical situations, this means that the implementation should register itself with - * platform-specific event sources onListen and deregister again onCancel. - */ -export interface StreamHandler { - /** - * Handles a request to set up an event stream. - * - * Any uncaught exception thrown by this method will be caught by the channel implementation - * and logged. An error result message will be sent back to Flutter. - * - * @param args - Stream configuration arguments, possibly null - * @param events - An {@link EventSink} for emitting events to the Flutter receiver - */ - onListen(args: Any, events: EventSink): void; - - /** - * Handles a request to tear down the most recently created event stream. - * - * Any uncaught exception thrown by this method will be caught by the channel implementation - * and logged. An error result message will be sent back to Flutter. - * - * The channel implementation may call this method with null arguments to separate a pair of - * two consecutive set up requests. Such request pairs may occur during Flutter hot restart. Any - * uncaught exception thrown in this situation will be logged without notifying Flutter. - * - * @param args - Stream configuration arguments, possibly null - */ - onCancel(args: Any): void; -} - -/** - * Event callback. Supports dual use: Producers of events to be sent to Flutter act as clients of - * this interface for sending events. Consumers of events sent from Flutter implement this - * interface for handling received events (the latter facility has not been implemented yet). - */ -export interface EventSink { - /** - * Consumes a successful event. - * - * @param event - The event, possibly null - */ - success(event: Any): void; - - /** - * Consumes an error event. - * - * @param errorCode - An error code string - * @param errorMessage - A human-readable error message, possibly null - * @param errorDetails - Error details, possibly null - */ - error(errorCode: string, errorMessage: string, errorDetails: Any): void; - - /** - * Consumes end of stream. Ensuing calls to success or error, if any, are ignored. - */ - endOfStream(): void; -} - -/** - * Internal handler for incoming stream requests from Flutter. - */ -class IncomingStreamRequestHandler implements BinaryMessageHandler { - private handler: StreamHandler; - private activeSink = new AtomicReference(null); - private codec: MethodCodec; - private name: string; - private messenger: BinaryMessenger; - - /** - * Constructs a new IncomingStreamRequestHandler instance. - * @param handler - The StreamHandler to delegate to - * @param name - The channel name - * @param codec - The MethodCodec to use for encoding/decoding - * @param messenger - The BinaryMessenger to use for sending events - */ - constructor(handler: StreamHandler, name: string, codec: MethodCodec, messenger: BinaryMessenger) { - this.handler = handler; - this.codec = codec; - this.name = name; - this.messenger = messenger; - } - - /** - * Handles a binary stream request from Flutter. - * @param message - The binary message containing the request - * @param reply - The BinaryReply callback to send a response - */ - onMessage(message: ArrayBuffer, reply: BinaryReply): void { - const call = this.codec.decodeMethodCall(message); - if (call.method == "listen") { - this.onListen(call.args, reply); - } else if (call.method == "cancel") { - this.onCancel(call.args, reply); - } else { - reply.reply(null); - } - } - - /** - * Handles a request to set up an event stream. - * @param args - Stream configuration arguments - * @param callback - The BinaryReply callback to send a response - */ - onListen(args: Any, callback: BinaryReply): void { - const eventSink = new EventSinkImplementation(this.activeSink, this.name, this.codec, this.messenger); - const oldSink = this.activeSink.getAndSet(eventSink); - if (oldSink != null) { - // Repeated calls to onListen may happen during hot restart. - // We separate them with a call to onCancel. - try { - this.handler.onCancel(null); - } catch (e) { - Log.e(TAG + this.name, "Failed to close existing event stream", e); - } - } - try { - this.handler.onListen(args, eventSink); - callback.reply(this.codec.encodeSuccessEnvelope(null)); - } catch (e) { - this.activeSink.set(null); - Log.e(TAG + this.name, "Failed to open event stream", e); - callback.reply(this.codec.encodeErrorEnvelope("error", e.getMessage(), null)); - } - } - - /** - * Handles a request to tear down an event stream. - * @param args - Stream configuration arguments - * @param callback - The BinaryReply callback to send a response - */ - onCancel(args: Any, callback: BinaryReply): void { - const oldSink = this.activeSink.getAndSet(null); - if (oldSink != null) { - try { - this.handler.onCancel(args); - callback.reply(this.codec.encodeSuccessEnvelope(null)); - } catch (e) { - Log.e(TAG + this.name, "Failed to close event stream", e); - callback.reply(this.codec.encodeErrorEnvelope("error", e.getMessage(), null)); - } - } else { - callback.reply(this.codec.encodeErrorEnvelope("error", "No active stream to cancel", null)); - } - } -} - -/** - * Implementation of EventSink for sending events to Flutter. - */ -class EventSinkImplementation implements EventSink { - private hasEnded = false; - private activeSink: AtomicReference; - private messenger: BinaryMessenger; - private codec: MethodCodec; - private name: string; - - /** - * Constructs a new EventSinkImplementation instance. - * @param activeSink - The AtomicReference to track the active sink - * @param name - The channel name - * @param codec - The MethodCodec to use for encoding - * @param messenger - The BinaryMessenger to use for sending events - */ - constructor(activeSink: AtomicReference, name: string, codec: MethodCodec, messenger: BinaryMessenger) { - this.activeSink = activeSink; - this.codec = codec; - this.name = name; - this.messenger = messenger; - } - - /** - * Sends a successful event to Flutter. - * @param event - The event data, possibly null - */ - success(event: Any): void { - if (this.hasEnded || this.activeSink.get() != this) { - return; - } - this.messenger.send(this.name, this.codec.encodeSuccessEnvelope(event)); - } - - /** - * Sends an error event to Flutter. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - */ - error(errorCode: string, errorMessage: string, errorDetails: Any) { - if (this.hasEnded || this.activeSink.get() != this) { - return; - } - this.messenger.send( - this.name, this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails)); - } - - /** - * Signals the end of the event stream. - */ - endOfStream(): void { - if (this.hasEnded || this.activeSink.get() != this) { - return; - } - this.hasEnded = true; - this.messenger.send(this.name, new ArrayBuffer(0)); - } -} - -/** - * A simple atomic reference implementation for thread-safe value access. - * @template T - The type of value being referenced - */ -class AtomicReference { - private value: T | null; - - /** - * Constructs a new AtomicReference instance. - * @param value - The initial value, possibly null - */ - constructor(value: T | null) { - this.value = value - } - - /** - * Gets the current value. - * @returns The current value, possibly null - */ - get(): T | null { - return this.value; - } - - /** - * Sets a new value. - * @param newValue - The new value to set, possibly null - */ - set(newValue: T | null): void { - this.value = newValue; - } - - /** - * Atomically gets the current value and sets a new value. - * @param newValue - The new value to set, possibly null - * @returns The old value, possibly null - */ - getAndSet(newValue: T | null) { - const oldValue = this.value; - this.value = newValue; - return oldValue; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/FlutterException.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/FlutterException.ets deleted file mode 100644 index 31f8525..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/FlutterException.ets +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterException.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import Any from './Any'; - -/** - * Exception class for Flutter platform channel errors. - * This class represents errors that occur during communication between Flutter and the platform. - */ -export default class FlutterException implements Error { - /** Optional stack trace for the error. */ - stack?: string; - /** The error message. */ - message: string; - /** The error name. */ - name: string = ""; - /** The error code. */ - code: string; - /** Additional error details. */ - details: Any - - /** - * Constructs a new FlutterException instance. - * @param code - The error code - * @param message - The error message - * @param details - Additional error details - */ - constructor(code: string, message: string, details: Any) { - this.message = message; - this.code = code; - this.details = details; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/JSONMessageCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/JSONMessageCodec.ets deleted file mode 100644 index 054e1c7..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/JSONMessageCodec.ets +++ /dev/null @@ -1,111 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on JSONMessageCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import StringUtils from '../../util/StringUtils'; - -import MessageCodec from './MessageCodec'; -import StringCodec from './StringCodec'; -import TreeMap from '@ohos.util.TreeMap'; -import HashMap from '@ohos.util.HashMap'; -import LightWeightMap from '@ohos.util.LightWeightMap'; -import PlainArray from '@ohos.util.PlainArray'; -import List from '@ohos.util.List'; -import LinkedList from '@ohos.util.LinkedList'; -import Any from './Any'; - -/** - * A {@link MessageCodec} using UTF-8 encoded JSON messages. - * - * This codec is guaranteed to be compatible with the corresponding `JSONMessageCodec` on the Dart side. - * These parts of the Flutter SDK are evolved synchronously. - * - * On the Dart side, JSON messages are handled by the JSON facilities of the `dart:convert` package. - */ -export default class JSONMessageCodec implements MessageCodec { - /** Singleton instance of JSONMessageCodec. */ - static INSTANCE = new JSONMessageCodec(); - - /** - * Encodes a message into JSON binary format. - * @param message - The message to encode, possibly null - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: Any): ArrayBuffer { - if (message == null) { - return StringUtils.stringToArrayBuffer(""); - } - return StringCodec.INSTANCE.encodeMessage(JSON.stringify(this.toBaseData(message))); - } - - /** - * Decodes a binary message from JSON format. - * @param message - The binary message to decode, possibly null - * @returns The decoded message object - * @throws Error if the JSON is invalid - */ - decodeMessage(message: ArrayBuffer | null): Any { - if (message == null) { - return StringUtils.stringToArrayBuffer(""); - } - try { - const jsonStr = StringCodec.INSTANCE.decodeMessage(message); - let jsonObj: Record = JSON.parse(jsonStr); - if (jsonObj instanceof Object) { - const list = Object.keys(jsonObj); - if (list.includes('args')) { - let args: Any = jsonObj['args']; - if (args instanceof Object && !(args instanceof Array)) { - let argsMap: Map = new Map(); - Object.keys(args).forEach(key => { - argsMap.set(key, args[key]); - }) - jsonObj['args'] = argsMap; - } - } - } - return jsonObj; - } catch (e) { - throw new Error("Invalid JSON"); - } - } - - /** - * Converts a message to base data types suitable for JSON serialization. - * @param message - The message to convert - * @returns The converted message with base data types - */ - toBaseData(message: Any): Any { - if (message == null || message == undefined) { - return null; - } else if (message instanceof List || message instanceof LinkedList) { - return this.toBaseData(message.convertToArray()); - } else if (message instanceof Map || message instanceof HashMap || message instanceof TreeMap - || message instanceof LightWeightMap || message instanceof PlainArray) { - let messageObj: Any = {}; - message.forEach((value: Any, key: Any) => { - messageObj[this.toBaseData(key)] = this.toBaseData(value); - }); - return messageObj; - } else if (message instanceof Array) { - let messageArr: Array = []; - message.forEach((value: Any) => { - messageArr.push(this.toBaseData(value)); - }) - return messageArr; - } else if (message instanceof Object) { - let messageObj: Any = {}; - Object.keys(message).forEach((key: Any) => { - messageObj[this.toBaseData(key)] = this.toBaseData(message[key]); - }) - return messageObj; - } else { - return message; - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/JSONMethodCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/JSONMethodCodec.ets deleted file mode 100644 index 97fb142..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/JSONMethodCodec.ets +++ /dev/null @@ -1,132 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on JSONMethodCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import Log from '../../util/Log'; - -import ToolUtils from '../../util/ToolUtils'; -import FlutterException from './FlutterException'; -import Any from './Any'; -import JSONMessageCodec from './JSONMessageCodec'; -import MethodCall from './MethodCall'; -import MethodCodec from './MethodCodec'; - -/** - * A {@link MethodCodec} using UTF-8 encoded JSON method calls and result envelopes. - * - * This codec is guaranteed to be compatible with the corresponding `JSONMethodCodec` on - * the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * Values supported as methods arguments and result payloads are those supported by {@link JSONMessageCodec}. - */ -export default class JSONMethodCodec implements MethodCodec { - /** Singleton instance of JSONMethodCodec. */ - static INSTANCE = new JSONMethodCodec(); - - /** - * Encodes a method call into JSON binary format. - * @param methodCall - The MethodCall to encode - * @returns The encoded method call as an ArrayBuffer - * @throws Error if encoding fails - */ - encodeMethodCall(methodCall: MethodCall): ArrayBuffer { - try { - const map: Record = { - "method": methodCall.method, "args": methodCall.args - } - - return JSONMessageCodec.INSTANCE.encodeMessage(map); - } catch (e) { - throw new Error("Invalid JSON"); - } - } - - /** - * Decodes a method call from JSON binary format. - * @param message - The binary message to decode - * @returns The decoded MethodCall - * @throws Error if decoding fails or the message is invalid - */ - decodeMethodCall(message: ArrayBuffer): MethodCall { - try { - const json: Any = JSONMessageCodec.INSTANCE.decodeMessage(message); - if (ToolUtils.isObj(json)) { - const method: string = json["method"]; - const args: Any = json["args"]; - if (typeof method == 'string') { - return new MethodCall(method, args); - } - } - throw new Error("Invalid method call: " + json); - } catch (e) { - throw new Error("Invalid JSON:" + JSON.stringify(e)); - } - } - - /** - * Encodes a successful result into a JSON envelope. - * @param result - The result value, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeSuccessEnvelope(result: Any): ArrayBuffer { - return JSONMessageCodec.INSTANCE.encodeMessage([result]); - } - - /** - * Encodes an error result into a JSON envelope. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelope(errorCode: Any, errorMessage: string, errorDetails: Any): ArrayBuffer { - return JSONMessageCodec.INSTANCE.encodeMessage([errorCode, errorMessage, errorDetails]); - } - - /** - * Encodes an error result into a JSON envelope with stacktrace. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @param errorStacktrace - The platform stacktrace, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: Any, - errorStacktrace: string): ArrayBuffer { - return JSONMessageCodec.INSTANCE.encodeMessage([errorCode, errorMessage, errorDetails, errorStacktrace]) - } - - /** - * Decodes a result envelope from JSON binary format. - * @param envelope - The binary envelope to decode - * @returns The decoded result value - * @throws FlutterException if the envelope contains an error - * @throws Error if the envelope is invalid - */ - decodeEnvelope(envelope: ArrayBuffer): Any { - try { - const json: Any = JSONMessageCodec.INSTANCE.decodeMessage(envelope); - if (json instanceof Array) { - if (json.length == 1) { - return json[0]; - } - if (json.length == 3) { - const code: string = json[0]; - const message: string = json[1]; - const details: Any = json[2]; - if (typeof code == 'string' && (message == null || typeof message == 'string')) { - throw new FlutterException(code, message, details); - } - } - } - throw new Error("Invalid envelope: " + json); - } catch (e) { - throw new Error("Invalid JSON"); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MessageCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MessageCodec.ets deleted file mode 100644 index cbd3690..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MessageCodec.ets +++ /dev/null @@ -1,29 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MessageCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * A message encoding/decoding mechanism. - * @template T - The type of message being encoded/decoded - */ -export default interface MessageCodec { - /** - * Encodes the specified message into binary. - * @param message - The message to encode - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: T): ArrayBuffer; - - /** - * Decodes the specified message from binary. - * @param message - The binary message to decode, possibly null - * @returns The decoded message - */ - decodeMessage(message: ArrayBuffer | null): T; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall.ets deleted file mode 100644 index de9e96e..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCall.ets +++ /dev/null @@ -1,76 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MethodCall.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import ToolUtils from '../../util/ToolUtils'; -import TreeMap from '@ohos.util.TreeMap'; -import HashMap from '@ohos.util.HashMap'; -import LightWeightMap from '@ohos.util.LightWeightMap'; -import Any from './Any'; - -/** - * Command object representing a method call on a MethodChannel. - */ -export default class MethodCall { - /** The name of the called method. */ - method: string; - /** - * Arguments for the call. - * - * Consider using the argument() method for cases where a particular run-time type is expected. - * Consider using argument(key) when that run-time type is Map or Object. - */ - args: Any; - - /** - * Constructs a new MethodCall instance. - * @param method - The name of the method to call - * @param args - The arguments for the method call - */ - constructor(method: string, args: Any) { - this.method = method; - this.args = args; - } - - /** - * Gets an argument value by key. - * @param key - The argument key - * @returns The argument value, or null if not found - * @throws Error if the args cannot be cast to a Map or Object - */ - argument(key: string): Any { - if (this.args == null) { - return null; - } else if (this.args instanceof Map) { - return (this.args as Map).get(key); - } else if (ToolUtils.isObj(this.args)) { - return this.args[key]; - } else { - throw new Error("ClassCastException"); - } - } - - /** - * Checks if an argument exists for the given key. - * @param key - The argument key to check - * @returns True if the argument exists, false otherwise - * @throws Error if the args cannot be cast to a Map or Object - */ - hasArgument(key: string): boolean { - if (this.args == null) { - return false; - } else if (this.args instanceof Map) { - return (this.args as Map).has(key); - } else if (ToolUtils.isObj(this.args)) { - return this.args.hasOwnProperty(key); - } else { - throw new Error("ClassCastException"); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel.ets deleted file mode 100644 index b8fcab6..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel.ets +++ /dev/null @@ -1,231 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MethodChannel.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Log from '../../util/Log'; -import MessageChannelUtils from '../../util/MessageChannelUtils'; -import StringUtils from '../../util/StringUtils'; -import { BinaryMessageHandler, BinaryMessenger, BinaryReply } from './BinaryMessenger'; -import Any from './Any'; -import MethodCall from './MethodCall'; -import MethodCodec from './MethodCodec'; -import StandardMethodCodec from './StandardMethodCodec'; - -/** - * A named channel for communicating with the Flutter application using asynchronous method calls. - * - * Incoming method calls are decoded from binary on receipt, and results are encoded into - * binary before being transmitted back to Flutter. The {@link MethodCodec} used must be compatible - * with the one used by the Flutter application. This can be achieved by creating a `MethodChannel` counterpart of this - * channel on the Dart side. The type of method call arguments and results is {@code Any}, - * but only values supported by the specified {@link MethodCodec} can be used. - * - * The logical identity of the channel is given by its name. Identically named channels will - * interfere with each other's communication. - */ - -export default class MethodChannel { - /** Tag for logging. */ - static TAG = "MethodChannel#"; - private messenger: BinaryMessenger; - private name: string; - private codec: MethodCodec; - - /** - * Constructs a new MethodChannel instance. - * @param messenger - The BinaryMessenger to use for communication - * @param name - The channel name - * @param codec - The MethodCodec to use for encoding/decoding, defaults to StandardMethodCodec.INSTANCE - */ - constructor(messenger: BinaryMessenger, name: string, codec: MethodCodec = StandardMethodCodec.INSTANCE) { - this.messenger = messenger - this.name = name - this.codec = codec - } - - /** - * Invokes a method on this channel, optionally expecting a result. - * - * Any uncaught exception thrown by the result callback will be caught and logged. - * - * @param method - The name of the method - * @param args - The arguments for the invocation, possibly null - * @param callback - A {@link MethodResult} callback for the invocation result, or null - */ - invokeMethod(method: string, args: Any, callback?: MethodResult): void { - this.messenger.send(this.name, this.codec.encodeMethodCall(new MethodCall(method, args)), - callback == null ? null : new IncomingResultHandler(callback, this.codec)); - } - - /** - * Registers a method call handler on this channel. - * - * Overrides any existing handler registration for (the name of) this channel. - * - * If no handler has been registered, any incoming method call on this channel will be handled - * silently by sending a null reply. This results in a `MissingPluginException` on the Dart side, unless an - * `OptionalMethodChannel` is used. - * - * @param handler - A {@link MethodCallHandler}, or null to deregister - */ - setMethodCallHandler(handler: MethodCallHandler | null): void { - this.messenger.setMessageHandler(this.name, - handler == null ? null : new IncomingMethodCallHandler(handler, this.codec)); - } - - /** - * Adjusts the number of messages that will get buffered when sending messages to channels that - * aren't fully set up yet. For example, the engine isn't running yet or the channel's message - * handler isn't set up on the Dart side yet. - * @param newSize - The new buffer size - */ - resizeChannelBuffer(newSize: number): void { - MessageChannelUtils.resizeChannelBuffer(this.messenger, this.name, newSize); - } -} - -/** A handler of incoming method calls. */ -export interface MethodCallHandler { - /** - * Handles the specified method call received from Flutter. - * - * Handler implementations must submit a result for all incoming calls, by making a single - * call on the given {@link MethodResult} callback. Failure to do so will result in lingering Flutter - * result handlers. The result may be submitted asynchronously and on any thread. Calls to - * unknown or unimplemented methods should be handled using the notImplemented method of {@link MethodResult}. - * - * Any uncaught exception thrown by this method will be caught by the channel implementation - * and logged, and an error result will be sent back to Flutter. - * - * The handler is called on the platform thread (OpenHarmony main thread) by default, or otherwise on the thread - * specified by the {@link TaskQueue} provided to the associated {@link MethodChannel} when it was created. - * - * @param call - A {@link MethodCall} - * @param result - A {@link MethodResult} used for submitting the result of the call - */ - onMethodCall(call: MethodCall, result: MethodResult): void; -} - -/** - * Method call result callback. Supports dual use: Implementations of methods to be invoked by - * Flutter act as clients of this interface for sending results back to Flutter. Invokers of - * Flutter methods provide implementations of this interface for handling results received from - * Flutter. - * - * All methods of this class can be invoked on any thread. - */ -export interface MethodResult { - /** - * Handles a successful result. - * - * @param result - The result, possibly null. The result must be an Object type supported by the - * codec. For instance, if you are using {@link StandardMessageCodec} (default), please see - * its documentation on what types are supported. - */ - success: (result: Any) => void; - - /** - * Handles an error result. - * - * @param errorCode - An error code string - * @param errorMessage - A human-readable error message, possibly null - * @param errorDetails - Error details, possibly null. The details must be an Object type - * supported by the codec. For instance, if you are using {@link StandardMessageCodec} - * (default), please see its documentation on what types are supported. - */ - error: (errorCode: string, errorMessage: string, errorDetails: Any) => void; - - /** Handles a call to an unimplemented method. */ - notImplemented: () => void; -} - -/** - * Internal handler for incoming method call results from Flutter. - */ -export class IncomingResultHandler implements BinaryReply { - private callback: MethodResult; - private codec: MethodCodec; - - /** - * Constructs a new IncomingResultHandler instance. - * @param callback - The MethodResult callback to invoke - * @param codec - The MethodCodec to use for decoding - */ - constructor(callback: MethodResult, codec: MethodCodec) { - this.callback = callback; - this.codec = codec - } - - /** - * Handles a binary reply from Flutter. - * @param reply - The binary reply, possibly null - */ - reply(reply: ArrayBuffer | null): void { - try { - if (reply == null) { - this.callback.notImplemented(); - } else { - try { - this.callback.success(this.codec.decodeEnvelope(reply)); - } catch (e) { - this.callback.error(e.code, e.getMessage(), e.details); - } - } - } catch (e) { - Log.e(MethodChannel.TAG, "Failed to handle method call result", e); - } - } -} - -/** - * Internal handler for incoming method calls from Flutter. - */ -export class IncomingMethodCallHandler implements BinaryMessageHandler { - private handler: MethodCallHandler; - private codec: MethodCodec; - - /** - * Constructs a new IncomingMethodCallHandler instance. - * @param handler - The MethodCallHandler to delegate to - * @param codec - The MethodCodec to use for encoding/decoding - */ - constructor(handler: MethodCallHandler, codec: MethodCodec) { - this.handler = handler; - this.codec = codec - } - - /** - * Handles a binary method call from Flutter. - * @param message - The binary message containing the method call - * @param reply - The BinaryReply callback to send a response - */ - onMessage(message: ArrayBuffer, reply: BinaryReply): void { - const call = this.codec.decodeMethodCall(message); - try { - this.handler.onMethodCall( - call, { - success: (result: Any): void => { - reply.reply(this.codec.encodeSuccessEnvelope(result)); - }, - - error: (errorCode: string, errorMessage: string, errorDetails: Any): void => { - reply.reply(this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails)); - }, - - notImplemented: (): void => { - Log.w(MethodChannel.TAG, "method not implemented"); - reply.reply(null); - } - }); - } catch (e) { - Log.e(MethodChannel.TAG, "Failed to handle method call", e); - reply.reply(this.codec.encodeErrorEnvelopeWithStacktrace("error", e.getMessage(), null, e)); - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCodec.ets deleted file mode 100644 index 00efea1..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/MethodCodec.ets +++ /dev/null @@ -1,79 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MethodCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import Any from './Any'; - -import MethodCall from './MethodCall'; - -/** - * A codec for method calls and enveloped results. - * - * Method calls are encoded as binary messages with enough structure that the codec can extract a - * method name and arguments. These data items are used to populate a {@link MethodCall}. - * - * All operations throw an Error if conversion fails. - */ -export default interface MethodCodec { - /** - * Encodes a message call into binary. - * - * @param methodCall - A {@link MethodCall} - * @returns An {@link ArrayBuffer} containing the encoded method call - */ - encodeMethodCall(methodCall: MethodCall): ArrayBuffer; - - /** - * Decodes a message call from binary. - * - * @param methodCall - The binary encoding of the method call as an {@link ArrayBuffer} - * @returns A {@link MethodCall} representation of the binary data - */ - decodeMethodCall(methodCall: ArrayBuffer): MethodCall; - - /** - * Encodes a successful result into a binary envelope message. - * - * @param result - The result value, possibly null - * @returns An {@link ArrayBuffer} containing the encoded envelope - */ - encodeSuccessEnvelope(result: Any): ArrayBuffer; - - /** - * Encodes an error result into a binary envelope message. - * - * @param errorCode - An error code string - * @param errorMessage - An error message string, possibly null - * @param errorDetails - Error details, possibly null. Consider supporting {@link Error} in your - * codec. This is the most common value passed to this field. - * @returns An {@link ArrayBuffer} containing the encoded envelope - */ - encodeErrorEnvelope(errorCode: string, errorMessage: string, errorDetails: Any): ArrayBuffer; - - /** - * Encodes an error result into a binary envelope message with the native stacktrace. - * - * @param errorCode - An error code string - * @param errorMessage - An error message string, possibly null - * @param errorDetails - Error details, possibly null. Consider supporting {@link Error} in your - * codec. This is the most common value passed to this field. - * @param errorStacktrace - Platform stacktrace for the error, possibly null - * @returns An {@link ArrayBuffer} containing the encoded envelope - */ - encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: Any, - errorStacktrace: string): ArrayBuffer - - /** - * Decodes a result envelope from binary. - * - * @param envelope - The binary encoding of a result envelope as an {@link ArrayBuffer} - * @returns The enveloped result value - * @throws FlutterException if the envelope was an error envelope - */ - decodeEnvelope(envelope: ArrayBuffer): Any -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableBinaryCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableBinaryCodec.ets deleted file mode 100644 index 5a174d5..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableBinaryCodec.ets +++ /dev/null @@ -1,63 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on BinaryCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import SendableMessageCodec from './SendableMessageCodec'; - -/** - * A {@link SendableMessageCodec} using unencoded binary messages, represented as {@link ArrayBuffer}s. - * - * This codec is guaranteed to be compatible with the corresponding `BinaryCodec` on the - * Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * On the Dart side, messages are represented using {@code ByteData}. - */ - -/** - * A sendable MessageCodec using unencoded binary messages. - * This codec can be used in background threads and worker contexts. - */ -@Sendable -export default class SendableBinaryCodec implements SendableMessageCodec { - private returnsDirectByteBufferFromDecoding: boolean = false; - /** Direct instance that returns the direct buffer from decoding. */ - static readonly INSTANCE_DIRECT: SendableBinaryCodec = new SendableBinaryCodec(true); - - /** - * Constructs a new SendableBinaryCodec instance. - * @param returnsDirectByteBufferFromDecoding - Whether to return the direct buffer from decoding - */ - constructor(returnsDirectByteBufferFromDecoding: boolean) { - this.returnsDirectByteBufferFromDecoding = returnsDirectByteBufferFromDecoding; - } - - /** - * Encodes a binary message (no-op for binary codec). - * @param message - The ArrayBuffer message to encode - * @returns The same ArrayBuffer - */ - encodeMessage(message: ArrayBuffer): ArrayBuffer { - return message - } - - /** - * Decodes a binary message. - * @param message - The ArrayBuffer message to decode, possibly null - * @returns The decoded ArrayBuffer, or a copy depending on configuration - */ - decodeMessage(message: ArrayBuffer | null): ArrayBuffer { - if (message == null) { - return new ArrayBuffer(0); - } else if (this.returnsDirectByteBufferFromDecoding) { - return message; - } else { - return message.slice(0, message.byteLength); - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableBinaryMessageHandler.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableBinaryMessageHandler.ets deleted file mode 100644 index 10a9c05..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableBinaryMessageHandler.ets +++ /dev/null @@ -1,23 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import { lang } from '@kit.ArkTS'; -import { BinaryReply } from './BinaryMessenger'; - -type ISendable = lang.ISendable; - -/** - * Interface for sendable binary message handlers that can be used in background threads. - * This interface extends ISendable to allow handlers to be passed across thread boundaries. - */ -export default interface SendableBinaryMessageHandler extends ISendable { - /** - * Handles a binary message received from Flutter. - * @param message - The binary message received - * @param reply - The reply callback to send a response - * @param args - Additional arguments passed to the handler - */ - onMessage(message: ArrayBuffer, reply: BinaryReply, ...args: Object[]): void; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableJSONMessageCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableJSONMessageCodec.ets deleted file mode 100644 index 6870cba..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableJSONMessageCodec.ets +++ /dev/null @@ -1,116 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on JSONMessageCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import StringUtils from '../../util/StringUtils'; - -import SendableMessageCodec from './SendableMessageCodec'; -import StringCodec from './StringCodec'; -import TreeMap from '@ohos.util.TreeMap'; -import HashMap from '@ohos.util.HashMap'; -import LightWeightMap from '@ohos.util.LightWeightMap'; -import PlainArray from '@ohos.util.PlainArray'; -import List from '@ohos.util.List'; -import LinkedList from '@ohos.util.LinkedList'; -import Any from './Any'; - -/** - * A {@link SendableMessageCodec} using UTF-8 encoded JSON messages. - * - * This codec is guaranteed to be compatible with the corresponding `JSONMessageCodec` on - * the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * On the Dart side, JSON messages are handled by the JSON facilities of the `dart:convert` package. - */ -/** - * A sendable MessageCodec using UTF-8 encoded JSON messages. - * This codec can be used in background threads and worker contexts. - */ -@Sendable -export default class SendableJSONMessageCodec implements SendableMessageCodec { - /** Singleton instance of SendableJSONMessageCodec. */ - static INSTANCE: SendableJSONMessageCodec = new SendableJSONMessageCodec(); - - /** - * Encodes a message into JSON binary format. - * @param message - The message to encode, possibly null - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: Any): ArrayBuffer { - if (message == null) { - return StringUtils.stringToArrayBuffer(""); - } - return StringCodec.INSTANCE.encodeMessage(JSON.stringify(this.toBaseData(message))); - } - - /** - * Decodes a binary message from JSON format. - * @param message - The binary message to decode, possibly null - * @returns The decoded message object - * @throws Error if the JSON is invalid - */ - decodeMessage(message: ArrayBuffer | null): Any { - if (message == null) { - return StringUtils.stringToArrayBuffer(""); - } - try { - const jsonStr = StringCodec.INSTANCE.decodeMessage(message); - let jsonObj: Record = JSON.parse(jsonStr); - if (jsonObj instanceof Object) { - const list = Object.keys(jsonObj); - if (list.includes('args')) { - let args: Any = jsonObj['args']; - if (args instanceof Object && !(args instanceof Array)) { - let argsMap: Map = new Map(); - Object.keys(args).forEach(key => { - argsMap.set(key, args[key]); - }) - jsonObj['args'] = argsMap; - } - } - } - return jsonObj; - } catch (e) { - throw new Error("Invalid JSON"); - } - } - - /** - * Converts a message to base data types suitable for JSON serialization. - * @param message - The message to convert - * @returns The converted message with base data types - */ - toBaseData(message: Any): Any { - if (message == null || message == undefined) { - return ""; - } else if (message instanceof List || message instanceof LinkedList) { - return this.toBaseData(message.convertToArray()); - } else if (message instanceof Map || message instanceof HashMap || message instanceof TreeMap - || message instanceof LightWeightMap || message instanceof PlainArray) { - let messageObj: Any = {}; - message.forEach((value: Any, key: Any) => { - messageObj[this.toBaseData(key)] = this.toBaseData(value); - }); - return messageObj; - } else if (message instanceof Array) { - let messageArr: Array = []; - message.forEach((value: Any) => { - messageArr.push(this.toBaseData(value)); - }) - return messageArr; - } else if (message instanceof Object) { - let messageObj: Any = {}; - Object.keys(message).forEach((key: Any) => { - messageObj[this.toBaseData(key)] = this.toBaseData(message[key]); - }) - return messageObj; - } else { - return message; - } - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableJSONMethodCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableJSONMethodCodec.ets deleted file mode 100644 index 250b1c9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableJSONMethodCodec.ets +++ /dev/null @@ -1,135 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on JSONMethodCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import ToolUtils from '../../util/ToolUtils'; -import FlutterException from './FlutterException'; -import Any from './Any'; -import SendableJSONMessageCodec from './SendableJSONMessageCodec'; -import MethodCall from './MethodCall'; -import SendableMethodCodec from './SendableMethodCodec'; - -/** - * A {@link SendableMethodCodec} using UTF-8 encoded JSON method calls and result envelopes. - * - * This codec is guaranteed to be compatible with the corresponding `JSONMethodCodec` on - * the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * Values supported as methods arguments and result payloads are those supported by {@link SendableJSONMessageCodec}. - */ -/** - * A sendable MethodCodec using UTF-8 encoded JSON method calls and result envelopes. - * This codec can be used in background threads and worker contexts. - */ -@Sendable -export default class SendableJSONMethodCodec implements SendableMethodCodec { - /** Singleton instance of SendableJSONMethodCodec. */ - static INSTANCE: SendableJSONMethodCodec = new SendableJSONMethodCodec(); - - /** - * Encodes a method call into JSON binary format. - * @param methodCall - The MethodCall to encode - * @returns The encoded method call as an ArrayBuffer - * @throws Error if encoding fails - */ - encodeMethodCall(methodCall: MethodCall): ArrayBuffer { - try { - const map: Record = { - "method": methodCall.method, "args": methodCall.args - } - - return SendableJSONMessageCodec.INSTANCE.encodeMessage(map); - } catch (e) { - throw new Error("Invalid JSON"); - } - } - - /** - * Decodes a method call from JSON binary format. - * @param message - The binary message to decode - * @returns The decoded MethodCall - * @throws Error if decoding fails or the message is invalid - */ - decodeMethodCall(message: ArrayBuffer): MethodCall { - try { - const json: Any = SendableJSONMessageCodec.INSTANCE.decodeMessage(message); - if (ToolUtils.isObj(json)) { - const method: string = json["method"]; - const args: Any = json["args"]; - if (typeof method == 'string') { - return new MethodCall(method, args); - } - } - throw new Error("Invalid method call: " + json); - } catch (e) { - throw new Error("Invalid JSON:" + JSON.stringify(e)); - } - } - - /** - * Encodes a successful result into a JSON envelope. - * @param result - The result value, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeSuccessEnvelope(result: Any): ArrayBuffer { - return SendableJSONMessageCodec.INSTANCE.encodeMessage([result]); - } - - /** - * Encodes an error result into a JSON envelope. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelope(errorCode: Any, errorMessage: string, errorDetails: Any) { - return SendableJSONMessageCodec.INSTANCE.encodeMessage([errorCode, errorMessage, errorDetails]); - } - - /** - * Encodes an error result into a JSON envelope with stacktrace. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @param errorStacktrace - The platform stacktrace, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: Any, - errorStacktrace: string): ArrayBuffer { - return SendableJSONMessageCodec.INSTANCE.encodeMessage([errorCode, errorMessage, errorDetails, errorStacktrace]) - } - - /** - * Decodes a result envelope from JSON binary format. - * @param envelope - The binary envelope to decode - * @returns The decoded result value - * @throws FlutterException if the envelope contains an error - * @throws Error if the envelope is invalid - */ - decodeEnvelope(envelope: ArrayBuffer): Any { - try { - const json: Any = SendableJSONMessageCodec.INSTANCE.decodeMessage(envelope); - if (json instanceof Array) { - if (json.length == 1) { - return json[0]; - } - if (json.length == 3) { - const code: string = json[0]; - const message: string = json[1]; - const details: Any = json[2]; - if (typeof code == 'string' && (message == null || typeof message == 'string')) { - throw new FlutterException(code, message, details); - } - } - } - throw new Error("Invalid envelope: " + json); - } catch (e) { - throw new Error("Invalid JSON"); - } - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMessageCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMessageCodec.ets deleted file mode 100644 index 953de40..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMessageCodec.ets +++ /dev/null @@ -1,33 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MessageCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import { lang } from '@kit.ArkTS'; - -type ISendable = lang.ISendable; - -/** - * Interface for sendable message codecs that can be used in background threads. - * This interface extends ISendable to allow codecs to be passed across thread boundaries. - * @template T - The type of message being encoded/decoded - */ -export default interface SendableMessageCodec extends ISendable { - /** - * Encodes the specified message into binary. - * @param message - The message to encode - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: T): ArrayBuffer; - - /** - * Decodes the specified message from binary. - * @param message - The binary message to decode, possibly null - * @returns The decoded message - */ - decodeMessage(message: ArrayBuffer | null): T; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMessageHandler.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMessageHandler.ets deleted file mode 100644 index 763067b..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMessageHandler.ets +++ /dev/null @@ -1,23 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import { lang } from '@kit.ArkTS'; -import { Reply } from './BasicMessageChannel'; - -type ISendable = lang.ISendable; - -/** - * Interface for sendable message handlers that can be used in background threads. - * This interface extends ISendable to allow handlers to be passed across thread boundaries. - * @template T - The type of message being handled - */ -export default interface SendableMessageHandler extends ISendable { - /** - * Handles a message received from Flutter. - * @param message - The message received - * @param reply - The reply callback to send a response - */ - onMessage(message: T, reply: Reply): void; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMethodCallHandler.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMethodCallHandler.ets deleted file mode 100644 index 2d9be00..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMethodCallHandler.ets +++ /dev/null @@ -1,38 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import { lang } from '@kit.ArkTS'; - -import MethodCall from './MethodCall'; -import { MethodResult } from './MethodChannel'; - -type ISendable = lang.ISendable; - -/** - * Interface for sendable method call handlers that can be used in background threads. - * This interface extends ISendable to allow handlers to be passed across thread boundaries. - */ -export default interface SendableMethodCallHandler extends ISendable { - /** - * Handles the specified method call received from Flutter. - * - * Handler implementations must submit a result for all incoming calls, by making a single - * call on the given {@link MethodResult} callback. Failure to do so will result in lingering Flutter - * result handlers. The result may be submitted asynchronously and on any thread. Calls to - * unknown or unimplemented methods should be handled using {@link MethodResult#notImplemented()}. - * - * Any uncaught exception thrown by this method will be caught by the channel implementation - * and logged, and an error result will be sent back to Flutter. - * - * The handler is called on the platform thread (OpenHarmony main thread) by default, or - * otherwise on the thread specified by the {@link BinaryMessenger.TaskQueue} provided to the - * associated {@link MethodChannel} when it was created. - * - * @param call - A {@link MethodCall} - * @param result - A {@link MethodResult} used for submitting the result of the call - * @param args - Additional arguments - */ - onMethodCall(call: MethodCall, result: MethodResult, ...args: Object[]): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMethodCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMethodCodec.ets deleted file mode 100644 index d7f9e69..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableMethodCodec.ets +++ /dev/null @@ -1,82 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MethodCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import { lang } from '@kit.ArkTS'; - -import Any from './Any'; -import MethodCall from './MethodCall'; - -/** - * A codec for method calls and enveloped results. - * - * Method calls are encoded as binary messages with enough structure that the codec can extract a - * method name and arguments. These data items are used to populate a {@link MethodCall}. - * - * All operations throw an Error if conversion fails. - */ -type ISendable = lang.ISendable; - -export default interface SendableMethodCodec extends ISendable { - /** - * Encodes a message call into binary. - * - * @param methodCall - A {@link MethodCall} - * @returns An {@link ArrayBuffer} containing the encoded method call - */ - encodeMethodCall(methodCall: MethodCall): ArrayBuffer; - - /** - * Decodes a message call from binary. - * - * @param methodCall - The binary encoding of the method call as an {@link ArrayBuffer} - * @returns A {@link MethodCall} representation of the binary data - */ - decodeMethodCall(methodCall: ArrayBuffer): MethodCall; - - /** - * Encodes a successful result into a binary envelope message. - * - * @param result - The result value, possibly null - * @returns An {@link ArrayBuffer} containing the encoded envelope - */ - encodeSuccessEnvelope(result: Any): ArrayBuffer; - - /** - * Encodes an error result into a binary envelope message. - * - * @param errorCode - An error code string - * @param errorMessage - An error message string, possibly null - * @param errorDetails - Error details, possibly null. Consider supporting {@link Error} in your - * codec. This is the most common value passed to this field. - * @returns An {@link ArrayBuffer} containing the encoded envelope - */ - encodeErrorEnvelope(errorCode: string, errorMessage: string, errorDetails: Any): ArrayBuffer; - - /** - * Encodes an error result into a binary envelope message with the native stacktrace. - * - * @param errorCode - An error code string - * @param errorMessage - An error message string, possibly null - * @param errorDetails - Error details, possibly null. Consider supporting {@link Error} in your - * codec. This is the most common value passed to this field. - * @param errorStacktrace - Platform stacktrace for the error, possibly null - * @returns An {@link ArrayBuffer} containing the encoded envelope - */ - encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: Any, - errorStacktrace: string): ArrayBuffer; - - /** - * Decodes a result envelope from binary. - * - * @param envelope - The binary encoding of a result envelope as an {@link ArrayBuffer} - * @returns The enveloped result value - * @throws FlutterException if the envelope was an error envelope - */ - decodeEnvelope(envelope: ArrayBuffer): Any; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStandardMessageCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStandardMessageCodec.ets deleted file mode 100644 index f1e2278..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStandardMessageCodec.ets +++ /dev/null @@ -1,413 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on StandardMessageCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Any from './Any'; - -import { ByteBuffer } from '../../util/ByteBuffer'; -import SendableMessageCodec from './SendableMessageCodec'; -import StringUtils from '../../util/StringUtils'; -import TreeMap from '@ohos.util.TreeMap'; -import HashMap from '@ohos.util.HashMap'; -import LightWeightMap from '@ohos.util.LightWeightMap'; -import PlainArray from '@ohos.util.PlainArray'; -import List from '@ohos.util.List'; -import LinkedList from '@ohos.util.LinkedList'; - -/** - * MessageCodec using the Flutter standard binary encoding. - * - * This codec is guaranteed to be compatible with the corresponding `SendableStandardMessageCodec` - * on the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * Supported messages are acyclic values of these forms: - * null - * Booleans - * number - * BigIntegers (see below) - * Int8Array, Int32Array, Float32Array, Float64Array - * Strings - * Array[] - * Lists of supported values - * Maps with supported keys and values - * - * On the Dart side, these values are represented as follows: - * null: null - * Boolean: bool - * Byte, Short, Integer, Long: int - * Float, Double: double - * String: String - * byte[]: Uint8List - * int[]: Int32List - * long[]: Int64List - * float[]: Float32List - * double[]: Float64List - * List: List - * Map: Map - * - * BigIntegers are represented in Dart as strings with the hexadecimal representation of the - * integer's value. - * - * To extend the codec, overwrite the writeValue and readValueOfType methods. - */ -/** - * A sendable MessageCodec using the Flutter standard binary encoding. - * This codec can be used in background threads and worker contexts. - */ -@Sendable -export default class SendableStandardMessageCodec implements SendableMessageCodec { - /** Singleton instance of SendableStandardMessageCodec. */ - static INSTANCE: SendableStandardMessageCodec = new SendableStandardMessageCodec(); - - /** - * Encodes a message into binary format using the standard encoding. - * @param message - The message to encode - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: Any): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)) - this.writeValue(stream, message); - return stream.buffer - } - - /** - * Decodes a binary message from the standard encoding. - * @param message - The binary message to decode, possibly null - * @returns The decoded message - */ - decodeMessage(message: ArrayBuffer | null): Any { - if (message == null) { - return null - } - const buffer = ByteBuffer.from(message) - return this.readValue(buffer) - } - - private static NULL: number = 0; - private static TRUE: number = 1; - private static FALSE: number = 2; - private static INT32: number = 3; - private static INT64: number = 4; - private static BIGINT: number = 5; - private static FLOAT64: number = 6; - private static STRING: number = 7; - private static UINT8_ARRAY: number = 8; - private static INT32_ARRAY: number = 9; - private static INT64_ARRAY: number = 10; - private static FLOAT64_ARRAY: number = 11; - private static LIST: number = 12; - private static MAP: number = 13; - private static FLOAT32_ARRAY: number = 14; - - /** - * Writes a value to the byte buffer using the standard encoding. - * @param stream - The ByteBuffer to write to - * @param value - The value to write - * @returns The ByteBuffer stream - */ - writeValue(stream: ByteBuffer, value: Any): Any { - if (value == null || value == undefined) { - stream.writeInt8(SendableStandardMessageCodec.NULL); - } else if (typeof value === "boolean") { - stream.writeInt8(value ? SendableStandardMessageCodec.TRUE : SendableStandardMessageCodec.FALSE) - } else if (typeof value === "number") { - if (Number.isInteger(value)) { //整型 - if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { //int32 - stream.writeInt8(SendableStandardMessageCodec.INT32); - stream.writeInt32(value, true); - } else if (Number.MIN_SAFE_INTEGER <= value && value <= Number.MAX_SAFE_INTEGER) { //int64 number整型取值范围 - stream.writeInt8(SendableStandardMessageCodec.INT64); - stream.writeInt64(value, true); - } else { //被判为整型的double型 - stream.writeInt8(SendableStandardMessageCodec.FLOAT64); - this.writeAlignment(stream, 8); - stream.writeFloat64(value, true); - } - } else { //浮点型 - stream.writeInt8(SendableStandardMessageCodec.FLOAT64); - this.writeAlignment(stream, 8); - stream.writeFloat64(value, true); - } - } else if (typeof value === "bigint") { - // - // The format is first the type byte (0x05), then the actual number - // as an ASCII string giving the hexadecimal representation of the - // integer, with the string's length as encoded by writeSize - // followed by the string bytes. - stream.writeInt8(SendableStandardMessageCodec.BIGINT); - // Convert bigint to a hexadecimal string - const hexString = value.toString(16); - // Map each character in the hexadecimal string to its ASCII code - const asciiString = hexString.split('').map(char => char.charCodeAt(0)); - this.writeBytes(stream, Uint8Array.from(asciiString)); - } else if (typeof value === "string") { - stream.writeInt8(SendableStandardMessageCodec.STRING); - let stringBuff = StringUtils.stringToArrayBuffer(value); - this.writeBytes(stream, new Uint8Array(stringBuff)); - } else if (value instanceof Uint8Array) { - stream.writeInt8(SendableStandardMessageCodec.UINT8_ARRAY); - this.writeBytes(stream, value) - } else if (value instanceof Int32Array) { - stream.writeInt8(SendableStandardMessageCodec.INT32_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 4); - value.forEach(item => stream.writeInt32(item, true)); - } else if (value instanceof BigInt64Array) { - stream.writeInt8(SendableStandardMessageCodec.INT64_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 8); - value.forEach(item => stream.writeBigInt64(item, true)); - } else if (value instanceof Float32Array) { - stream.writeInt8(SendableStandardMessageCodec.FLOAT32_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 4); - value.forEach(item => stream.writeFloat32(item, true)); - } else if (value instanceof Float64Array) { - stream.writeInt8(SendableStandardMessageCodec.FLOAT64_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 8); - value.forEach(item => stream.writeFloat64(item, true)); - } else if (value instanceof Array || value instanceof Int8Array || value instanceof Int16Array - || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof List - || value instanceof LinkedList) { - stream.writeInt8(SendableStandardMessageCodec.LIST) - this.writeSize(stream, value.length); - value.forEach((item: Any): void => this.writeValue(stream, item)); - } else if (value instanceof Map) { - stream.writeInt8(SendableStandardMessageCodec.MAP); - this.writeSize(stream, value.size); - value.forEach((value: Any, key: Any) => { - this.writeValue(stream, key); - this.writeValue(stream, value); - }); - } else if (value instanceof HashMap || value instanceof TreeMap || value instanceof LightWeightMap - || value instanceof PlainArray) { - stream.writeInt8(SendableStandardMessageCodec.MAP); - this.writeSize(stream, value.length); - value.forEach((value: Any, key: Any) => { - this.writeValue(stream, key); - this.writeValue(stream, value); - }); - } else if (typeof value == 'object') { - let map: Map = new Map(); - Object.keys(value).forEach(key => { - map.set(key, value[key]); - }); - this.writeValue(stream, map); - } else { - throw new Error("Unsupported value: " + value); - stream.writeInt8(SendableStandardMessageCodec.NULL); - } - return stream; - } - - /** - * Writes alignment padding to the stream. - * @param stream - The ByteBuffer to write to - * @param alignment - The alignment requirement (e.g., 4, 8) - */ - writeAlignment(stream: ByteBuffer, alignment: number) { - let mod: number = stream.byteOffset % alignment; - if (mod != 0) { - for (let i = 0; i < alignment - mod; i++) { - stream.writeInt8(0); - } - } - } - - /** - * Writes a size value to the stream using compact encoding. - * @param stream - The ByteBuffer to write to - * @param value - The size value to write - */ - writeSize(stream: ByteBuffer, value: number) { - if (value < 254) { - stream.writeUint8(value); - } else if (value <= 0xffff) { - stream.writeUint8(254); - stream.writeUint16(value, true); - } else { - stream.writeUint8(255); - stream.writeUint32(value, true); - } - } - - /** - * Writes a byte array to the stream. - * @param stream - The ByteBuffer to write to - * @param bytes - The byte array to write - */ - writeBytes(stream: ByteBuffer, bytes: Uint8Array) { - this.writeSize(stream, bytes.length) - stream.writeUint8Array(bytes); - } - - /** - * Reads a size value from the buffer using compact encoding. - * @param buffer - The ByteBuffer to read from - * @returns The size value - */ - readSize(buffer: ByteBuffer) { - let value = buffer.readUint8() & 0xff; - if (value < 254) { - return value; - } else if (value == 254) { - return buffer.readUint16(true); - } else { - return buffer.readUint32(true); - } - } - - /** - * Reads alignment padding from the buffer. - * @param buffer - The ByteBuffer to read from - * @param alignment - The alignment requirement (e.g., 4, 8) - */ - readAlignment(buffer: ByteBuffer, alignment: number) { - let mod = buffer.byteOffset % alignment; - if (mod != 0) { - buffer.skip(alignment - mod); - } - } - - /** - * Reads a value from the buffer using the standard encoding. - * @param buffer - The ByteBuffer to read from - * @returns The decoded value - */ - readValue(buffer: ByteBuffer): Any { - let type = buffer.readUint8() - return this.readValueOfType(type, buffer); - } - - /** - * Reads a byte array from the buffer. - * @param buffer - The ByteBuffer to read from - * @returns The byte array - */ - readBytes(buffer: ByteBuffer): Uint8Array { - let length = this.readSize(buffer); - let bytesBuffer = new ArrayBuffer(length); - let bytes = new Uint8Array(bytesBuffer); - bytes.set(buffer.readUint8Array(length)); - return bytes; - } - - /** - * Reads a value of a specific type from the buffer. - * @param type - The type code to read - * @param buffer - The ByteBuffer to read from - * @returns The decoded value - * @throws Error if the type is unknown or the message is corrupted - */ - readValueOfType(type: number, buffer: ByteBuffer): Any { - let result: Any; - switch (type) { - case SendableStandardMessageCodec.NULL: - result = null; - break; - case SendableStandardMessageCodec.TRUE: - result = true; - break; - case SendableStandardMessageCodec.FALSE: - result = false; - break; - case SendableStandardMessageCodec.INT32: - result = buffer.readInt32(true); - break; - case SendableStandardMessageCodec.INT64: - result = buffer.readInt64(true); - if (Number.MIN_SAFE_INTEGER <= result && result <= Number.MAX_SAFE_INTEGER) { - result = Number(result); - } - break; - case SendableStandardMessageCodec.BIGINT: - let bytes: Uint8Array = this.readBytes(buffer); - // Convert the byte array to a UTF-8 encoded string - const hexString: string = String.fromCharCode(...bytes); - // Parse the string as a hexadecimal BigInt - result = BigInt(`0x${hexString}`); - break; - case SendableStandardMessageCodec.FLOAT64: - this.readAlignment(buffer, 8); - result = buffer.readFloat64(true) - break; - case SendableStandardMessageCodec.STRING: { - let bytes: Uint8Array = this.readBytes(buffer); - result = StringUtils.uint8ArrayToString(bytes); - break; - } - case SendableStandardMessageCodec.UINT8_ARRAY: { - result = this.readBytes(buffer); - break; - } - case SendableStandardMessageCodec.INT32_ARRAY: { - let length = this.readSize(buffer); - let array = new Int32Array(length) - this.readAlignment(buffer, 4); - for (let i = 0; i < length; i++) { - array[i] = buffer.readInt32(true) - } - result = array; - break; - } - case SendableStandardMessageCodec.INT64_ARRAY: { - let length = this.readSize(buffer); - let array = new BigInt64Array(length) - this.readAlignment(buffer, 8); - for (let i = 0; i < length; i++) { - array[i] = buffer.readBigInt64(true) - } - result = array; - break; - } - case SendableStandardMessageCodec.FLOAT64_ARRAY: { - let length = this.readSize(buffer); - let array = new Float64Array(length) - this.readAlignment(buffer, 8); - for (let i = 0; i < length; i++) { - array[i] = buffer.readFloat64(true) - } - result = array; - break; - } - case SendableStandardMessageCodec.LIST: { - let length = this.readSize(buffer); - let array: Array = new Array(length) - for (let i = 0; i < length; i++) { - array[i] = this.readValue(buffer) - } - result = array; - break; - } - case SendableStandardMessageCodec.MAP: { - let size = this.readSize(buffer); - let map: Map = new Map() - for (let i = 0; i < size; i++) { - map.set(this.readValue(buffer), this.readValue(buffer)); - } - result = map; - break; - } - case SendableStandardMessageCodec.FLOAT32_ARRAY: { - let length = this.readSize(buffer); - let array = new Float32Array(length); - this.readAlignment(buffer, 4); - for (let i = 0; i < length; i++) { - array[i] = buffer.readFloat32(true) - } - result = array; - break; - } - default: - throw new Error("Message corrupted, type=" + type); - } - return result; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStandardMethodCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStandardMethodCodec.ets deleted file mode 100644 index dd31884..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStandardMethodCodec.ets +++ /dev/null @@ -1,158 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on StandardMethodCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { ByteBuffer } from '../../util/ByteBuffer'; -import FlutterException from './FlutterException'; -import Any from './Any'; -import MethodCall from './MethodCall'; -import SendableMethodCodec from './SendableMethodCodec'; -import SendableStandardMessageCodec from './SendableStandardMessageCodec'; - -/** - * A {@link SendableMethodCodec} using the Flutter standard binary encoding. - * - * This codec is guaranteed to be compatible with the corresponding `StandardMethodCodec` - * on the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * Values supported as method arguments and result payloads are those supported by {@link StandardMessageCodec}. - */ -/** - * A sendable MethodCodec using the Flutter standard binary encoding. - * This codec can be used in background threads and worker contexts. - */ -@Sendable -export default class SendableStandardMethodCodec implements SendableMethodCodec { - private static TAG: string = "SendableStandardMethodCodec"; - /** Singleton instance of SendableStandardMethodCodec. */ - public static INSTANCE: SendableStandardMethodCodec = - new SendableStandardMethodCodec(SendableStandardMessageCodec.INSTANCE); - private messageCodec: SendableStandardMessageCodec; - - /** - * Creates a new method codec based on the specified message codec. - * @param messageCodec - The SendableStandardMessageCodec to use for encoding/decoding - */ - constructor(messageCodec: SendableStandardMessageCodec) { - this.messageCodec = messageCodec; - } - - /** - * Encodes a method call into binary format. - * @param methodCall - The MethodCall to encode - * @returns The encoded method call as an ArrayBuffer - */ - encodeMethodCall(methodCall: MethodCall): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - this.messageCodec.writeValue(stream, methodCall.method); - this.messageCodec.writeValue(stream, methodCall.args); - return stream.buffer; - } - - /** - * Decodes a method call from binary format. - * @param methodCall - The binary message to decode - * @returns The decoded MethodCall - * @throws Error if the method call is corrupted - */ - decodeMethodCall(methodCall: ArrayBuffer): MethodCall { - const buffer = ByteBuffer.from(methodCall); - const method: Any = this.messageCodec.readValue(buffer); - const args: Any = this.messageCodec.readValue(buffer); - if (typeof method == 'string' && !buffer.hasRemaining()) { - return new MethodCall(method, args); - } - throw new Error("Method call corrupted"); - } - - /** - * Encodes a successful result into a binary envelope. - * @param result - The result value, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeSuccessEnvelope(result: Any): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - stream.writeInt8(0); - this.messageCodec.writeValue(stream, result); - return stream.buffer; - } - - /** - * Encodes an error result into a binary envelope. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelope(errorCode: string, errorMessage: string, errorDetails: Any): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - stream.writeInt8(1); - this.messageCodec.writeValue(stream, errorCode); - this.messageCodec.writeValue(stream, errorMessage); - if (errorDetails instanceof Error) { - this.messageCodec.writeValue(stream, errorDetails.stack); - } else { - this.messageCodec.writeValue(stream, errorDetails); - } - return stream.buffer; - } - - /** - * Encodes an error result into a binary envelope with stacktrace. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @param errorStacktrace - The platform stacktrace, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: Any, - errorStacktrace: string): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - stream.writeInt8(1); - this.messageCodec.writeValue(stream, errorCode); - this.messageCodec.writeValue(stream, errorMessage); - if (errorDetails instanceof Error) { - this.messageCodec.writeValue(stream, errorDetails.stack); - } else { - this.messageCodec.writeValue(stream, errorDetails); - } - this.messageCodec.writeValue(stream, errorStacktrace); - return stream.buffer; - } - - /** - * Decodes a result envelope from binary format. - * @param envelope - The binary envelope to decode - * @returns The decoded result value - * @throws FlutterException if the envelope contains an error - * @throws Error if the envelope is corrupted - */ - decodeEnvelope(envelope: ArrayBuffer): Any { - const buffer = ByteBuffer.from(envelope); - const flag = buffer.readInt8(); - switch (flag) { - case 0: { - const result: Any = this.messageCodec.readValue(buffer); - if (!buffer.hasRemaining()) { - return result; - } - // Falls through intentionally. - } - case 1: { - const code: Any = this.messageCodec.readValue(buffer); - const message: Any = this.messageCodec.readValue(buffer); - const details: Any = this.messageCodec.readValue(buffer); - if (typeof code == 'string' && (message == null || typeof message == 'string') && !buffer.hasRemaining()) { - throw new FlutterException(code, message, details); - } - } - } - throw new Error("Envelope corrupted"); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStringCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStringCodec.ets deleted file mode 100644 index 8d53c83..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/SendableStringCodec.ets +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on StringCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import SendableMessageCodec from './SendableMessageCodec'; -import StringUtils from '../../util/StringUtils'; - -/** - * A {@link SendableMessageCodec} using UTF-8 encoded String messages. - * - * This codec is guaranteed to be compatible with the corresponding `StringCodec` on the Dart side. - * These parts of the Flutter SDK are evolved synchronously. - */ -/** - * A sendable MessageCodec using UTF-8 encoded String messages. - * This codec can be used in background threads and worker contexts. - */ -@Sendable -export default class SendableStringCodec implements SendableMessageCodec { - /** Singleton instance of SendableStringCodec. */ - static readonly INSTANCE: SendableStringCodec = new SendableStringCodec(); - - /** - * Encodes a string message into binary format. - * @param message - The string message to encode, possibly null - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: string): ArrayBuffer { - if (message == null) { - return StringUtils.stringToArrayBuffer(""); - } - return StringUtils.stringToArrayBuffer(message); - } - - /** - * Decodes a binary message into a string. - * @param message - The binary message to decode, possibly null - * @returns The decoded string - */ - decodeMessage(message: ArrayBuffer | null): string { - if (message == null) { - return ""; - } - return StringUtils.arrayBufferToString(message); - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec.ets deleted file mode 100644 index cf50f69..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec.ets +++ /dev/null @@ -1,416 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on StandardMessageCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { ByteBuffer } from '../../util/ByteBuffer'; -import StringUtils from '../../util/StringUtils'; -import MessageCodec from './MessageCodec'; -import TreeMap from '@ohos.util.TreeMap'; -import HashMap from '@ohos.util.HashMap'; -import LightWeightMap from '@ohos.util.LightWeightMap'; -import PlainArray from '@ohos.util.PlainArray'; -import List from '@ohos.util.List'; -import LinkedList from '@ohos.util.LinkedList'; -import Any from './Any'; -import { ArrayList } from '@kit.ArkTS'; - -/** - * MessageCodec using the Flutter standard binary encoding. - * - * This codec is guaranteed to be compatible with the corresponding `StandardMessageCodec` - * on the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * Supported messages are acyclic values of these forms: - * null - * Booleans - * number - * BigIntegers (see below) - * Int8Array, Int32Array, Float32Array, Float64Array - * Strings - * Array[] - * Lists of supported values - * Maps with supported keys and values - * - * - * On the Dart side, these values are represented as follows: - * null: null - * Boolean: bool - * Byte, Short, Integer, Long: int - * Float, Double: double - * String: String - * byte[]: Uint8List - * int[]: Int32List - * long[]: Int64List - * float[]: Float32List - * double[]: Float64List - * List: List - * Map: Map - * - * BigIntegers are represented in Dart as strings with the hexadecimal representation of the - * integer's value. - * - * To extend the codec, overwrite the writeValue and readValueOfType methods. - */ -export default class StandardMessageCodec implements MessageCodec { - private static TAG = "StandardMessageCodec#"; - /** Singleton instance of StandardMessageCodec. */ - static INSTANCE = new StandardMessageCodec(); - - /** - * Encodes a message into binary format using the standard encoding. - * @param message - The message to encode - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: Any): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)) - this.writeValue(stream, message); - return stream.buffer - } - - /** - * Decodes a binary message from the standard encoding. - * @param message - The binary message to decode, possibly null - * @returns The decoded message - */ - decodeMessage(message: ArrayBuffer | null): Any { - if (message == null) { - return null - } - const buffer = ByteBuffer.from(message) - return this.readValue(buffer) - } - - private static NULL = 0; - private static TRUE = 1; - private static FALSE = 2; - private static INT32 = 3; - private static INT64 = 4; - private static BIGINT = 5; - private static FLOAT64 = 6; - private static STRING = 7; - private static UINT8_ARRAY = 8; - private static INT32_ARRAY = 9; - private static INT64_ARRAY = 10; - private static FLOAT64_ARRAY = 11; - private static LIST = 12; - private static MAP = 13; - private static FLOAT32_ARRAY = 14; - private INT64_MAX = 9223372036854775807; - private INT64_MIN = -9223372036854775808; - - /** - * Writes a value to the byte buffer using the standard encoding. - * @param stream - The ByteBuffer to write to - * @param value - The value to write - * @returns The ByteBuffer stream - */ - writeValue(stream: ByteBuffer, value: Any): Any { - if (value == null || value == undefined) { - stream.writeInt8(StandardMessageCodec.NULL); - } else if (typeof value === "boolean") { - stream.writeInt8(value ? StandardMessageCodec.TRUE : StandardMessageCodec.FALSE) - } else if (typeof value === "number") { - if (Number.isInteger(value)) { //整型 - if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { //int32 - stream.writeInt8(StandardMessageCodec.INT32); - stream.writeInt32(value, true); - } else if (Number.MIN_SAFE_INTEGER <= value && value <= Number.MAX_SAFE_INTEGER) { //int64 number整型取值范围 - stream.writeInt8(StandardMessageCodec.INT64); - stream.writeInt64(value, true); - } else { //被判为整型的double型 - stream.writeInt8(StandardMessageCodec.FLOAT64); - this.writeAlignment(stream, 8); - stream.writeFloat64(value, true); - } - } else { //浮点型 - stream.writeInt8(StandardMessageCodec.FLOAT64); - this.writeAlignment(stream, 8); - stream.writeFloat64(value, true); - } - } else if (typeof value === "bigint") { - // The format is first the type byte (0x05), then the actual number - // as an ASCII string giving the hexadecimal representation of the - // integer, with the string's length as encoded by writeSize - // followed by the string bytes. - if (value >= this.INT64_MIN && value <= this.INT64_MAX) { - stream.writeInt8(StandardMessageCodec.INT64); - stream.writeBigInt64(value, true); - } else { - // Convert bigint to a hexadecimal string - stream.writeInt8(StandardMessageCodec.BIGINT); - const hexString = value.toString(16); - // Map each character in the hexadecimal string to its ASCII code - const asciiString = hexString.split('').map(char => char.charCodeAt(0)); - this.writeBytes(stream, Uint8Array.from(asciiString)); - } - } else if (typeof value === "string") { - stream.writeInt8(StandardMessageCodec.STRING); - let stringBuff = StringUtils.stringToArrayBuffer(value); - this.writeBytes(stream, new Uint8Array(stringBuff)); - } else if (value instanceof Uint8Array) { - stream.writeInt8(StandardMessageCodec.UINT8_ARRAY); - this.writeBytes(stream, value) - } else if (value instanceof Int32Array) { - stream.writeInt8(StandardMessageCodec.INT32_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 4); - value.forEach(item => stream.writeInt32(item, true)); - } else if (value instanceof BigInt64Array) { - stream.writeInt8(StandardMessageCodec.INT64_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 8); - value.forEach(item => stream.writeBigInt64(item, true)); - } else if (value instanceof Float32Array) { - stream.writeInt8(StandardMessageCodec.FLOAT32_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 4); - value.forEach(item => stream.writeFloat32(item, true)); - } else if (value instanceof Float64Array) { - stream.writeInt8(StandardMessageCodec.FLOAT64_ARRAY); - this.writeSize(stream, value.length); - this.writeAlignment(stream, 8); - value.forEach(item => stream.writeFloat64(item, true)); - } else if (value instanceof Array || value instanceof Int8Array || value instanceof Int16Array - || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof List - || value instanceof LinkedList || value instanceof ArrayList) { - stream.writeInt8(StandardMessageCodec.LIST) - this.writeSize(stream, value.length); - value.forEach((item: Any): void => this.writeValue(stream, item)); - } else if (value instanceof Map) { - stream.writeInt8(StandardMessageCodec.MAP); - this.writeSize(stream, value.size); - value.forEach((value: Any, key: Any) => { - this.writeValue(stream, key); - this.writeValue(stream, value); - }); - } else if (value instanceof HashMap || value instanceof TreeMap || value instanceof LightWeightMap - || value instanceof PlainArray) { - stream.writeInt8(StandardMessageCodec.MAP); - this.writeSize(stream, value.length); - value.forEach((value: Any, key: Any) => { - this.writeValue(stream, key); - this.writeValue(stream, value); - }); - } else if (typeof value == 'object') { - let map: Map = new Map(); - Object.keys(value).forEach(key => { - map.set(key, value[key]); - }); - this.writeValue(stream, map); - } else { - throw new Error("Unsupported value: " + value); - stream.writeInt8(StandardMessageCodec.NULL); - } - return stream; - } - - /** - * Writes alignment padding to the stream. - * @param stream - The ByteBuffer to write to - * @param alignment - The alignment requirement (e.g., 4, 8) - */ - writeAlignment(stream: ByteBuffer, alignment: number) { - let mod: number = stream.byteOffset % alignment; - if (mod != 0) { - for (let i = 0; i < alignment - mod; i++) { - stream.writeInt8(0); - } - } - } - - /** - * Writes a size value to the stream using compact encoding. - * @param stream - The ByteBuffer to write to - * @param value - The size value to write - */ - writeSize(stream: ByteBuffer, value: number) { - if (value < 254) { - stream.writeUint8(value); - } else if (value <= 0xffff) { - stream.writeUint8(254); - stream.writeUint16(value, true); - } else { - stream.writeUint8(255); - stream.writeUint32(value, true); - } - } - - /** - * Writes a byte array to the stream. - * @param stream - The ByteBuffer to write to - * @param bytes - The byte array to write - */ - writeBytes(stream: ByteBuffer, bytes: Uint8Array) { - this.writeSize(stream, bytes.length) - stream.writeUint8Array(bytes); - } - - /** - * Reads a size value from the buffer using compact encoding. - * @param buffer - The ByteBuffer to read from - * @returns The size value - */ - readSize(buffer: ByteBuffer) { - let value = buffer.readUint8() & 0xff; - if (value < 254) { - return value; - } else if (value == 254) { - return buffer.readUint16(true); - } else { - return buffer.readUint32(true); - } - } - - /** - * Reads alignment padding from the buffer. - * @param buffer - The ByteBuffer to read from - * @param alignment - The alignment requirement (e.g., 4, 8) - */ - readAlignment(buffer: ByteBuffer, alignment: number) { - let mod = buffer.byteOffset % alignment; - if (mod != 0) { - buffer.skip(alignment - mod); - } - } - - /** - * Reads a value from the buffer using the standard encoding. - * @param buffer - The ByteBuffer to read from - * @returns The decoded value - */ - readValue(buffer: ByteBuffer): Any { - let type = buffer.readUint8() - return this.readValueOfType(type, buffer); - } - - /** - * Reads a byte array from the buffer. - * @param buffer - The ByteBuffer to read from - * @returns The byte array - */ - readBytes(buffer: ByteBuffer): Uint8Array { - let length = this.readSize(buffer); - let bytesBuffer = new ArrayBuffer(length); - let bytes = new Uint8Array(bytesBuffer); - bytes.set(buffer.readUint8Array(length)); - return bytes; - } - - /** - * Reads a value of a specific type from the buffer. - * @param type - The type code to read - * @param buffer - The ByteBuffer to read from - * @returns The decoded value - * @throws Error if the type is unknown or the message is corrupted - */ - readValueOfType(type: number, buffer: ByteBuffer): Any { - let result: Any; - switch (type) { - case StandardMessageCodec.NULL: - result = null; - break; - case StandardMessageCodec.TRUE: - result = true; - break; - case StandardMessageCodec.FALSE: - result = false; - break; - case StandardMessageCodec.INT32: - result = buffer.readInt32(true); - break; - case StandardMessageCodec.INT64: - result = buffer.readInt64(true); - if (Number.MIN_SAFE_INTEGER <= result && result <= Number.MAX_SAFE_INTEGER) { - result = Number(result); - } - break; - case StandardMessageCodec.BIGINT: - let bytes: Uint8Array = this.readBytes(buffer); - // Convert the byte array to a UTF-8 encoded string - const hexString: string = String.fromCharCode(...bytes); - // Parse the string as a hexadecimal BigInt - result = BigInt(`0x${hexString}`); - break; - case StandardMessageCodec.FLOAT64: - this.readAlignment(buffer, 8); - result = buffer.readFloat64(true) - break; - case StandardMessageCodec.STRING: { - let bytes: Uint8Array = this.readBytes(buffer); - result = StringUtils.uint8ArrayToString(bytes); - break; - } - case StandardMessageCodec.UINT8_ARRAY: { - result = this.readBytes(buffer); - break; - } - case StandardMessageCodec.INT32_ARRAY: { - let length = this.readSize(buffer); - let array = new Int32Array(length) - this.readAlignment(buffer, 4); - for (let i = 0; i < length; i++) { - array[i] = buffer.readInt32(true) - } - result = array; - break; - } - case StandardMessageCodec.INT64_ARRAY: { - let length = this.readSize(buffer); - let array = new BigInt64Array(length) - this.readAlignment(buffer, 8); - for (let i = 0; i < length; i++) { - array[i] = buffer.readBigInt64(true) - } - result = array; - break; - } - case StandardMessageCodec.FLOAT64_ARRAY: { - let length = this.readSize(buffer); - let array = new Float64Array(length) - this.readAlignment(buffer, 8); - for (let i = 0; i < length; i++) { - array[i] = buffer.readFloat64(true) - } - result = array; - break; - } - case StandardMessageCodec.LIST: { - let length = this.readSize(buffer); - let array: Array = new Array(length) - for (let i = 0; i < length; i++) { - array[i] = this.readValue(buffer) - } - result = array; - break; - } - case StandardMessageCodec.MAP: { - let size = this.readSize(buffer); - let map: Map = new Map() - for (let i = 0; i < size; i++) { - map.set(this.readValue(buffer), this.readValue(buffer)); - } - result = map; - break; - } - case StandardMessageCodec.FLOAT32_ARRAY: { - let length = this.readSize(buffer); - let array = new Float32Array(length); - this.readAlignment(buffer, 4); - for (let i = 0; i < length; i++) { - array[i] = buffer.readFloat32(true) - } - result = array; - break; - } - default: - throw new Error("Message corrupted, type=" + type); - } - return result; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMethodCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMethodCodec.ets deleted file mode 100644 index f6059c0..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMethodCodec.ets +++ /dev/null @@ -1,152 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on StandardMethodCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { ByteBuffer } from '../../util/ByteBuffer'; -import FlutterException from './FlutterException'; -import Any from './Any'; -import MethodCall from './MethodCall'; -import MethodCodec from './MethodCodec'; -import StandardMessageCodec from './StandardMessageCodec'; - -/** - * A {@link MethodCodec} using the Flutter standard binary encoding. - * - * This codec is guaranteed to be compatible with the corresponding StandardMethodCodec - * on the Dart side. These parts of the Flutter SDK are evolved synchronously. - * - * Values supported as method arguments and result payloads are those supported by {@link StandardMessageCodec}. - */ -export default class StandardMethodCodec implements MethodCodec { - private static TAG = "StandardMethodCodec"; - /** Singleton instance of StandardMethodCodec. */ - public static INSTANCE = new StandardMethodCodec(StandardMessageCodec.INSTANCE); - private messageCodec: StandardMessageCodec; - - /** - * Creates a new method codec based on the specified message codec. - * @param messageCodec - The StandardMessageCodec to use for encoding/decoding - */ - constructor(messageCodec: StandardMessageCodec) { - this.messageCodec = messageCodec; - } - - /** - * Encodes a method call into binary format. - * @param methodCall - The MethodCall to encode - * @returns The encoded method call as an ArrayBuffer - */ - encodeMethodCall(methodCall: MethodCall): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - this.messageCodec.writeValue(stream, methodCall.method); - this.messageCodec.writeValue(stream, methodCall.args); - return stream.buffer; - } - - /** - * Decodes a method call from binary format. - * @param methodCall - The binary message to decode - * @returns The decoded MethodCall - * @throws Error if the method call is corrupted - */ - decodeMethodCall(methodCall: ArrayBuffer): MethodCall { - const buffer = ByteBuffer.from(methodCall); - const method: Any = this.messageCodec.readValue(buffer); - const args: Any = this.messageCodec.readValue(buffer); - if (typeof method == 'string' && !buffer.hasRemaining()) { - return new MethodCall(method, args); - } - throw new Error("Method call corrupted"); - } - - /** - * Encodes a successful result into a binary envelope. - * @param result - The result value, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeSuccessEnvelope(result: Any): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - stream.writeInt8(0); - this.messageCodec.writeValue(stream, result); - return stream.buffer; - } - - /** - * Encodes an error result into a binary envelope. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelope(errorCode: string, errorMessage: string, errorDetails: Any): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - stream.writeInt8(1); - this.messageCodec.writeValue(stream, errorCode); - this.messageCodec.writeValue(stream, errorMessage); - if (errorDetails instanceof Error) { - this.messageCodec.writeValue(stream, errorDetails.stack); - } else { - this.messageCodec.writeValue(stream, errorDetails); - } - return stream.buffer; - } - - /** - * Encodes an error result into a binary envelope with stacktrace. - * @param errorCode - The error code - * @param errorMessage - The error message, possibly null - * @param errorDetails - Error details, possibly null - * @param errorStacktrace - The platform stacktrace, possibly null - * @returns The encoded envelope as an ArrayBuffer - */ - encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: Any, - errorStacktrace: string): ArrayBuffer { - const stream = ByteBuffer.from(new ArrayBuffer(1024)); - stream.writeInt8(1); - this.messageCodec.writeValue(stream, errorCode); - this.messageCodec.writeValue(stream, errorMessage); - if (errorDetails instanceof Error) { - this.messageCodec.writeValue(stream, errorDetails.stack); - } else { - this.messageCodec.writeValue(stream, errorDetails); - } - this.messageCodec.writeValue(stream, errorStacktrace); - return stream.buffer; - } - - /** - * Decodes a result envelope from binary format. - * @param envelope - The binary envelope to decode - * @returns The decoded result value - * @throws FlutterException if the envelope contains an error - * @throws Error if the envelope is corrupted - */ - decodeEnvelope(envelope: ArrayBuffer): Any { - const buffer = ByteBuffer.from(envelope); - const flag = buffer.readInt8(); - switch (flag) { - case 0: { - const result: Any = this.messageCodec.readValue(buffer); - if (!buffer.hasRemaining()) { - return result; - } - // Falls through intentionally. - } - case 1: { - const code: Any = this.messageCodec.readValue(buffer); - const message: Any = this.messageCodec.readValue(buffer); - const details: Any = this.messageCodec.readValue(buffer); - if (typeof code == 'string' && (message == null || typeof message == 'string') && !buffer.hasRemaining()) { - throw new FlutterException(code, message, details); - } - } - } - throw new Error("Envelope corrupted"); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StringCodec.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StringCodec.ets deleted file mode 100644 index 2205dd7..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/common/StringCodec.ets +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on StringCodec.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import StringUtils from '../../util/StringUtils'; -import MessageCodec from './MessageCodec'; - -/** - * A {@link MessageCodec} using UTF-8 encoded String messages. - * - * This codec is guaranteed to be compatible with the corresponding `StringCodec` on the - * Dart side. These parts of the Flutter SDK are evolved synchronously. - */ -export default class StringCodec implements MessageCodec { - /** Singleton instance of StringCodec. */ - static readonly INSTANCE = new StringCodec(); - - /** - * Encodes a string message into binary format. - * @param message - The string message to encode, possibly null - * @returns The encoded message as an ArrayBuffer - */ - encodeMessage(message: string): ArrayBuffer { - if (message == null) { - return StringUtils.stringToArrayBuffer(""); - } - return StringUtils.stringToArrayBuffer(message); - } - - /** - * Decodes a binary message into a string. - * @param message - The binary message to decode, possibly null - * @returns The decoded string - */ - decodeMessage(message: ArrayBuffer | null): string { - if (message == null) { - return ""; - } - return StringUtils.arrayBufferToString(message); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/ListenableEditingState.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/ListenableEditingState.ets deleted file mode 100644 index 2a6858a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/ListenableEditingState.ets +++ /dev/null @@ -1,974 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on ListenableEditingState.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { TextEditState } from '../../embedding/engine/systemchannels/TextInputChannel'; -import Log from '../../util/Log'; -import inputMethod from '@ohos.inputMethod'; -import ArrayList from '@ohos.util.ArrayList'; -import { TextEditingDelta } from './TextEditingDelta'; -import TextInputChannel from '../../embedding/engine/systemchannels/TextInputChannel'; -import { FlutterTextUtils } from './TextUtils'; -import { KeyCode } from '@kit.InputKit'; -import KeyEventChannel, { SimulateKeyEvent } from '../../embedding/engine/systemchannels/KeyEventChannel'; -import { PasteboardUtils } from '../../util/PasteboardUtils'; - -const TAG = "ListenableEditingState"; - -/** - * Enumeration of delete operation states. - */ -enum DeleteStates { - /** Delete operation has started */ - START, - /** Delete operation is in progress */ - MOVING, - /** Delete operation has ended */ - END -} - -/** - * Manages the editable text state and notifies listeners of changes. - * This class tracks text content, selection, composing regions, and preview text. - */ -export class ListenableEditingState { - private TextInputChannel: TextInputChannel | null = null; - private keyEventChannel: KeyEventChannel | undefined; - private client: number = 0 - private leftDeleteState: DeleteStates = DeleteStates.END - private rightDeleteState: DeleteStates = DeleteStates.END - //Cache used to storage software keyboard input action - private mStringCache: string; - private mSelectionStartCache: number = 0; - private mSelectionEndCache: number = 0; - private mComposingStartCache: number = 0; - private mComposingEndCache: number = 0; - //used to compare with Cache - - private mListeners: ArrayList = new ArrayList(); - private mPendingListeners: ArrayList = new ArrayList(); - private mBatchTextEditingDeltas: ArrayList = new ArrayList(); - private mChangeNotificationDepth: number = 0; - private mBatchEditNestDepth: number = 0; - private mTextWhenBeginBatchEdit: string; - private mSelectionStartWhenBeginBatchEdit: number = 0; - private mSelectionEndWhenBeginBatchEdit: number = 0; - private mComposingStartWhenBeginBatchEdit: number = 0; - private mComposingEndWhenBeginBatchEdit: number = 0; - - // preview text - private mPreviewText: string = ""; - private mPreviewTextStart: number = 0; - private mPreviewTextEnd: number = 0; - private mLeftIdxOfPreviewTextRange: number = 0; - private mRightIdxOfPreviewTextRange: number = 0; - private mIsCursorIdxOutOfPreviewTextRange: boolean = false; - private mIsEnglishPreviewMode: boolean = false; - - /** - * Constructs a new ListenableEditingState instance. - * @param TextInputChannel - The TextInputChannel for communication, possibly null - * @param client - The client ID - * @param keyEventChannel - Optional KeyEventChannel for key event handling - */ - constructor(TextInputChannel:TextInputChannel | null,client:number, keyEventChannel?: KeyEventChannel) { - this.TextInputChannel = TextInputChannel; - this.keyEventChannel = keyEventChannel; - this.client = client - this.mStringCache = ""; - this.mTextWhenBeginBatchEdit = ""; - this.mSelectionStartCache = 0; - this.mSelectionEndCache = 0; - this.mComposingStartCache = -1; - this.mComposingEndCache = -1; - this.mPreviewText = ""; - } - - /** - * Extracts and clears the batch of text editing deltas. - * @returns A list of TextEditingDelta objects representing the changes - */ - extractBatchTextEditingDeltas(): ArrayList { - let currentBatchDeltas = new ArrayList(); - this.mBatchTextEditingDeltas.forEach((data) => { - currentBatchDeltas.add(data); - }) - this.mBatchTextEditingDeltas.clear(); - return currentBatchDeltas; - } - - /** - * Clears all batched text editing deltas. - */ - clearBatchDeltas(): void { - this.mBatchTextEditingDeltas.clear(); - } - - /** - * Replaces a range of text with new text. - * @param start - The start position of the range to replace - * @param end - The end position of the range to replace - * @param tb - The replacement text - * @param tbStart - The start position within the replacement text - * @param tbEnd - The end position within the replacement text - */ - replace(start: number, end: number, tb: String, tbStart: number, tbEnd: number): void { - const placeIndex = - this.mSelectionStartCache < this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - - this.mBatchTextEditingDeltas.add( - new TextEditingDelta( - this.mStringCache.toString(), - placeIndex + tbEnd, - placeIndex + tbEnd, - this.getComposingStart(), - this.getComposingEnd(), - start, - end + tbStart, - tb.toString() - )); - } - - /** - * Gets the current selection start position. - * @returns The selection start position - */ - getSelectionStart(): number { - return this.mSelectionStartCache; - } - - /** - * Gets the current selection end position. - * @returns The selection end position - */ - getSelectionEnd(): number { - return this.mSelectionEndCache; - } - - /** - * Gets the current composing region start position. - * @returns The composing start position, or -1 if no composing region - */ - getComposingStart(): number { - return this.mComposingStartCache; - } - - /** - * Gets the current composing region end position. - * @returns The composing end position, or -1 if no composing region - */ - getComposingEnd(): number { - return this.mComposingEndCache; - } - - /** - * Gets the current text content. - * @returns The text string - */ - getStringCache(): string { - return this.mStringCache; - } - - /** - * Gets the currently selected text. - * @returns The selected text, or empty string if selection is invalid - */ - getSelectionString(): string { - if (this.mSelectionStartCache < 0 || this.mSelectionEndCache > this.mStringCache.length) { - return ""; - } - return this.mStringCache.substring(this.mSelectionStartCache, this.mSelectionEndCache); - } - - /** - * Gets the current preview text from the input method. - * @returns The preview text - */ - getPreviewText(): string { - return this.mPreviewText; - } - - /** - * Gets the start position of the preview text in the original text. - * @returns The preview text start position - */ - getPreviewTextStart(): number { - return this.mPreviewTextStart; - } - - /** - * Gets the end position of the preview text in the original text. - * @returns The preview text end position - */ - getPreviewTextEnd(): number { - return this.mPreviewTextEnd; - } - - /** - * Gets the right index of the preview text range in the string cache. - * @returns The right index of the preview text range - */ - getRightIdxOfPreviewTextRange() : number { - return this.mRightIdxOfPreviewTextRange; - } - - /** - * Gets the left index of the preview text range in the string cache. - * @returns The left index of the preview text range - */ - getLeftIdxOfPreviewTextRange() : number { - return this.mLeftIdxOfPreviewTextRange; - } - - /** - * Sets the preview text. - * @param previewText - The preview text to set - */ - setPreviewText(previewText: string): void { - this.mPreviewText = previewText; - } - - /** - * Sets the start position of the preview text. - * @param previewTextStart - The start position - */ - setPreviewTextStart(previewTextStart: number): void { - this.mPreviewTextStart = previewTextStart; - } - - /** - * Sets the end position of the preview text. - * @param previewTextEnd - The end position - */ - setPreviewTextEnd(previewTextEnd: number): void { - this.mPreviewTextEnd = previewTextEnd; - } - - /** - * Updates the preview text range indices. - * @param leftIdx - The left index in the string cache - * @param rightIdx - The right index in the string cache - */ - updatePreviewTextRange(leftIdx: number, rightIdx: number): void { - this.mLeftIdxOfPreviewTextRange = leftIdx; - this.mRightIdxOfPreviewTextRange = rightIdx; - } - - /** - * Clears all preview text contents and resets related indices. - */ - clearPreviewTextContents() : void { - this.mPreviewText = ""; - this.mPreviewTextStart = 0; - this.mPreviewTextEnd = 0; - this.mLeftIdxOfPreviewTextRange = 0; - this.mRightIdxOfPreviewTextRange = 0; - } - - /** - * Sets whether the cursor index is out of the preview text range. - * @param hasChanged - True if the cursor is out of range, false otherwise - */ - setIsCursorIdxOutOfPreviewTextRange(hasChanged: boolean): void { - this.mIsCursorIdxOutOfPreviewTextRange = hasChanged; - } - - /** - * Gets whether the cursor index is out of the preview text range. - * @returns True if the cursor is out of range, false otherwise - */ - getIsCursorIdxOutOfPreviewTextRange(): boolean { - return this.mIsCursorIdxOutOfPreviewTextRange; - } - - /** - * Sets the selection start position. - * @param newSelectionStart - The new selection start position - */ - setSelectionStart(newSelectionStart: number): void { - this.mSelectionStartCache = newSelectionStart; - } - - /** - * Sets the selection end position. - * @param newSelectionEnd - The new selection end position - */ - setSelectionEnd(newSelectionEnd: number): void { - this.mSelectionEndCache = newSelectionEnd; - } - - /** - * Sets the composing region start position. - * @param newComposingStart - The new composing start position, or -1 to clear - */ - setComposingStart(newComposingStart: number): void { - this.mComposingStartCache = newComposingStart; - } - - /** - * Sets the composing region end position. - * @param newComposingEnd - The new composing end position, or -1 to clear - */ - setComposingEnd(newComposingEnd: number): void { - this.mComposingEndCache = newComposingEnd; - } - - /** - * Sets the text content. - * @param newStringCache - The new text content - */ - setStringCache(newStringCache: string): void { - this.mStringCache = newStringCache; - } - - /** - * Notifies a single listener of editing state changes. - * @param listener - The listener to notify - * @param textChanged - Whether the text has changed - * @param selectionChanged - Whether the selection has changed - * @param composingChanged - Whether the composing region has changed - */ - notifyListener(listener: EditingStateWatcher, - textChanged: boolean, - selectionChanged: boolean, - composingChanged: boolean): void { - this.mChangeNotificationDepth++; - listener.didChangeEditingState(textChanged, selectionChanged, composingChanged); - this.mChangeNotificationDepth--; - } - - /** - * Notifies all listeners if any changes have occurred. - * @param textChanged - Whether the text has changed - * @param selectionChanged - Whether the selection has changed - * @param composingChanged - Whether the composing region has changed - */ - notifyListenersIfNeeded(textChanged: boolean, selectionChanged: boolean, composingChanged: boolean) { - if (textChanged || selectionChanged || composingChanged) { - for (const listener of this.mListeners) { - this.notifyListener(listener, textChanged, selectionChanged, composingChanged); - } - } - } - - /** - * Handles insertion of preview text from the input method. - * @param text - The preview text to insert - * @param range - The range in the original text where preview text applies - */ - handleInsertPreviewTextEvent(text: string, range: inputMethod.Range): void { - // Determine whether it is in English preview input mode - if (range.start != -1 && range.end != -1 && text.indexOf("'") == -1) { - this.mIsEnglishPreviewMode = true; - } - // preview text callback with complete text contents,like a, a'b, a'b'c - // its text char do not appear one by one - this.setPreviewText(text); - this.setPreviewTextStart(range.start); - this.setPreviewTextEnd(range.end); - - // update preview text start idx and end idx - let leftIdxOfPreviewText: number = this.getSelectionStart(); - let rightIdxOfPreviewText: number = this.getSelectionStart() + text.length; - this.updatePreviewTextRange(leftIdxOfPreviewText, rightIdxOfPreviewText) - - if (this.mListeners == null) { - Log.e(TAG, "handleInsertPreviewTextEvent, mListeners is null"); - return; - } - this.notifyListenersIfNeeded(true, true, false); - } - - /** - * Handles insertion of final text from the input method. - * @param text - The text to insert - */ - handleInsertTextEvent(text: string): void { - // clear preview text cache before insert the final texts - if (this.getPreviewText().length != 0) { - this.setPreviewText(""); - } - // When previewTextChangeSelection async callback has been invoked, covering the previous preview text. - // But if that callback did not work, then manually insert the candidate word, covering the previous preview text. - if (this.getLeftIdxOfPreviewTextRange() < this.mSelectionStartCache && - (this.getLeftIdxOfPreviewTextRange() != this.getRightIdxOfPreviewTextRange())) { - this.mSelectionStartCache = this.getLeftIdxOfPreviewTextRange(); - this.mSelectionEndCache = this.getRightIdxOfPreviewTextRange(); - } else if (this.mIsEnglishPreviewMode) { - // To comply with the specifications of Xiaoyi-InputMethod, change the - // range of inserted English chars and add a space at the backend - this.mSelectionStartCache = this.getPreviewTextStart(); - this.mSelectionEndCache = this.getPreviewTextEnd(); - text += " "; - } - - let start = - this.mSelectionStartCache < this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - let end = this.mSelectionStartCache > this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - const length = text.length; - this.replace(start, end, text, 0, length); - - if (this.mStringCache.length == this.mSelectionStartCache) { - //Insert text one by one - let tempStr: string = this.mStringCache.substring(0, start) + text + this.mStringCache.substring(end); - this.mStringCache = tempStr; - this.setSelectionStart(this.mStringCache.length); - this.setSelectionEnd(this.mStringCache.length); - } else if (this.mStringCache.length > this.mSelectionStartCache) { - //Insert text in the middle of string - let tempStr: string = this.mStringCache.substring(0, start) + text + this.mStringCache.substring(end); - this.mStringCache = tempStr; - this.mSelectionStartCache = start + text.length; - this.mSelectionEndCache = this.mSelectionStartCache; - } - if (this.mListeners == null) { - Log.e(TAG, "mListeners is null"); - return; - } - this.notifyListenersIfNeeded(true, true, false); - // when preview text did insert, reset the params - this.updatePreviewTextRange(0, 0); - this.setPreviewTextStart(-1); - this.setPreviewTextEnd(-1); - this.mIsEnglishPreviewMode = false; - } - - /** - * Updates the text input state from Flutter. - * @param state - The new text edit state - */ - updateTextInputState(state: TextEditState): void { - if (this.leftDeleteState === DeleteStates.START) { - this.leftDeleteState = DeleteStates.MOVING; - } - if (this.rightDeleteState === DeleteStates.START) { - this.rightDeleteState = DeleteStates.MOVING; - } - this.beginBatchEdit(); - this.setStringCache(state.text); - if (state.hasSelection()) { - this.setSelectionStart(state.selectionStart); - this.setSelectionEnd(state.selectionEnd); - } else { - this.setSelectionStart(0); - this.setSelectionEnd(0); - } - this.endBatchEdit(); - } - - /** - * Begins a batch edit operation. - * Multiple edits can be batched together to reduce notifications. - */ - beginBatchEdit(): void { - this.mBatchEditNestDepth++; - if (this.mChangeNotificationDepth > 0) { - Log.e(TAG, "editing state should not be changed in a listener callback"); - } - if (this.mBatchEditNestDepth == 1 && !this.mListeners.isEmpty()) { - this.mTextWhenBeginBatchEdit = this.getStringCache(); - this.mSelectionStartWhenBeginBatchEdit = this.getSelectionStart(); - this.mSelectionEndWhenBeginBatchEdit = this.getSelectionEnd(); - this.mComposingStartWhenBeginBatchEdit = this.getComposingStart(); - this.mComposingEndWhenBeginBatchEdit = this.getComposingEnd(); - } - } - - /** - * Ends a batch edit operation and notifies listeners of all changes. - */ - endBatchEdit(): void { - if (this.mBatchEditNestDepth == 0) { - Log.e(TAG, "endBatchEdit called without a matching beginBatchEdit"); - return; - } - if (this.mBatchEditNestDepth == 1) { - Log.d(TAG, "mBatchEditNestDepth == 1"); - for (const listener of this.mPendingListeners) { - this.notifyListener(listener, true, true, true); - } - - if (!this.mListeners.isEmpty()) { - Log.d(TAG, "didFinishBatchEdit with " + this.mListeners.length + " listener(s)"); - const textChanged = !(this.mStringCache == this.mTextWhenBeginBatchEdit); - const selectionChanged = this.mSelectionStartWhenBeginBatchEdit != this.getSelectionStart() - || this.mSelectionEndWhenBeginBatchEdit != this.getSelectionEnd(); - const composingRegionChanged = this.mComposingStartWhenBeginBatchEdit != this.getComposingStart() - || this.mComposingEndWhenBeginBatchEdit != this.getComposingEnd(); - Log.d(TAG, "textChanged: " + textChanged + " selectionChanged: " + selectionChanged + - " composingRegionChanged: " + composingRegionChanged); - this.notifyListenersIfNeeded(textChanged, selectionChanged, composingRegionChanged); - } - } - for (const listener of this.mPendingListeners) { - this.mListeners.add(listener); - } - this.mPendingListeners.clear(); - this.mBatchEditNestDepth--; - } - - /** - * Adds a listener to be notified of editing state changes. - * @param listener - The listener to add - */ - addEditingStateListener(listener: EditingStateWatcher): void { - if (this.mChangeNotificationDepth > 0) { - Log.e(TAG, "adding a listener " + JSON.stringify(listener) + " in a listener callback"); - } - if (this.mBatchEditNestDepth > 0) { - Log.d(TAG, "a listener was added to EditingState while a batch edit was in progress"); - this.mPendingListeners.add(listener); - } else { - this.mListeners.add(listener); - } - } - - /** - * Removes an editing state listener. - * @param listener - The listener to remove - */ - removeEditingStateListener(listener: EditingStateWatcher): void { - if (this.mChangeNotificationDepth > 0) { - Log.e(TAG, "removing a listener " + JSON.stringify(listener) + " in a listener callback"); - } - this.mListeners.remove(listener); - if (this.mBatchEditNestDepth > 0) { - this.mPendingListeners.remove(listener); - } - } - - /** - * Marks the start of a deletion operation. - * @param code - The key code that triggered the deletion - */ - startDeleting(code: number) { - if (code === KeyCode.KEYCODE_FORWARD_DEL) { - this.rightDeleteState = DeleteStates.START - } else { - this.leftDeleteState = DeleteStates.START - } - } - - /** - * Marks the end of a deletion operation. - * @param code - The key code that triggered the deletion - */ - endDeletion(code: number) { - if (code === KeyCode.KEYCODE_FORWARD_DEL) { - this.rightDeleteState = DeleteStates.END - } else { - this.leftDeleteState = DeleteStates.END - } - } - - /** - * Handles a delete event from the input method. - * @param leftOrRight - True for delete right (forward delete), false for delete left (backspace) - * @param length - The number of characters to delete - * @param enableDeltaModel - Whether delta model is enabled - */ - handleDeleteEvent(leftOrRight: boolean, length: number, enableDeltaModel: boolean | undefined): void { - if (length === 0) { - return; - } - // clear preview text cache - if (this.getPreviewText().length != 0) { - this.setPreviewText(""); - } - - if (enableDeltaModel) { - let selectionStart = this.getSelectionStart(); - let selectionEnd = this.getSelectionEnd(); - if(selectionStart === 0 && selectionEnd === 0){ - // [selectionStart]和[selectionEnd]都为0时,删除文本范围为空 - return; - } - if (selectionStart === selectionEnd) { - // [selectionStart]和[selectionEnd]一致时为普通删除操作,需要修改偏移量 - this.setSelectionStart(this.mSelectionStartCache - length); - } - } - - let start = - this.mSelectionStartCache < this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - let end = this.mSelectionStartCache > this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - - if (leftOrRight == false && this.leftDeleteState !== DeleteStates.MOVING) { - //delete left - if (start == 0 && end == 0) { - return; - } - - let unicodeStart = start; - if (start == end) { - for (let i = 0; i < length; i++) { - unicodeStart = FlutterTextUtils.getOffsetBefore(this.mStringCache, unicodeStart); - if (unicodeStart === 0) { - break; - } - } - } - this.replace(unicodeStart, end, "", 0, 0); - this.mSelectionStartCache = unicodeStart; - let tempStr: string = this.mStringCache.slice(0, unicodeStart) + this.mStringCache.slice(end); - this.mStringCache = tempStr; - this.mSelectionEndCache = this.mSelectionStartCache; - } else if (leftOrRight == true && this.rightDeleteState !== DeleteStates.MOVING) { - //delete right - if (start == this.mStringCache.length) { - return; - } - let unicodeEnd = end; - if (start == end) { - for (let i = 0; i < length; i++) { - unicodeEnd = FlutterTextUtils.getOffsetAfter(this.mStringCache, unicodeEnd); - if (unicodeEnd === this.mStringCache.length) { - break; - } - } - } - this.replace(start, unicodeEnd, "", 0, 0); - this.mSelectionEndCache = start; - let tempStr: string = this.mStringCache.slice(0, start) + - (unicodeEnd >= this.mStringCache.length ? "" : this.mStringCache.slice(unicodeEnd)); - this.mStringCache = tempStr; - this.mSelectionStartCache = this.mSelectionEndCache; - } - this.notifyListenersIfNeeded(true, true, false); - } - - /** - * Handles a newline event from the input method. - */ - handleNewlineEvent(): void { - // 获取光标所在位置; - // 当光标移动前位置小于移动后的位置时,获取光标移动前位置;反之获取移动后位置 - let start = - this.mSelectionStartCache < this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - // 当光标移动前位置大于移动后的位置时,获取光标移动前位置;反之获取移动后位置 - let end = this.mSelectionStartCache > this.mSelectionEndCache ? this.mSelectionStartCache : this.mSelectionEndCache; - - this.replace(start, end, '\n', 0, 1); - // 对光标位置和字符串长度进行对比,决定光标位置的计算方法 - if (this.mStringCache.length == this.mSelectionStartCache) { - //Insert newline one by one - let tempStr: string = this.mStringCache.substring(0, start) + '\n' + this.mStringCache.substring(end); - this.mStringCache = tempStr; - this.setSelectionStart(this.mStringCache.length); - this.setSelectionEnd(this.mStringCache.length); - } else if (this.mStringCache.length > this.mSelectionStartCache) { - //Insert newline in the middle of string - let tempStr: string = this.mStringCache.substring(0, start) + '\n' + this.mStringCache.substring(end); - this.mStringCache = tempStr; - this.mSelectionStartCache = start + 1; - this.mSelectionEndCache = this.mSelectionStartCache; - } - if (this.mListeners == null) { - Log.e(TAG, "mListeners is null"); - return; - } - this.notifyListenersIfNeeded(true, true, false); - } - - /** - * Handles a function key event from the input method. - * @param functionKey - The function key that was pressed - */ - handleFunctionKey(functionKey: inputMethod.FunctionKey): void { - if (!this.TextInputChannel) { - return - } - switch (functionKey.enterKeyType) { - case inputMethod.EnterKeyType.PREVIOUS: - this.TextInputChannel.previous(this.client); - break; - case inputMethod.EnterKeyType.UNSPECIFIED: - this.TextInputChannel.unspecifiedAction(this.client); - break; - case inputMethod.EnterKeyType.NEWLINE: - this.TextInputChannel.newline(this.client); - break; - case inputMethod.EnterKeyType.GO: - this.TextInputChannel.go(this.client); - break; - case inputMethod.EnterKeyType.SEARCH: - this.TextInputChannel.search(this.client); - break; - case inputMethod.EnterKeyType.SEND: - this.TextInputChannel.send(this.client); - break; - case inputMethod.EnterKeyType.NEXT: - this.TextInputChannel.next(this.client); - break; - case inputMethod.EnterKeyType.DONE: - this.TextInputChannel.done(this.client); - break; - } - } - - /** - * Handles a text selection by range event. - * @param range - The range to select - */ - handleSelectByRangeEvent(range: inputMethod.Range): void { - if (range.start === 0) { // cursor index updated at the start pos of text - this.setSelectionStart(0); - this.setSelectionEnd(0); - } else { // cursor index updated at the end pos of text - this.setSelectionStart(this.getStringCache().length); - this.setSelectionEnd(this.getStringCache().length); - } - this.notifyListenersIfNeeded(false, true, false); - } - - /** - * cursor moved at 2D-text with 4 directions - * aaaaaa aaaaaa| - * bbbbbbb|b -(cursor_up)-> bbbbbbbb - * cccc cccc - * - * aaaaaa aaaaaa - * bbbbbbb|b -(cursor_down)-> bbbbbbbb - * cccc cccc| - * - * aaaaaa aaaaaa - * bbbbbbb|b -(cursor_right)-> bbbbbbbb| - * cccc cccc - * - * aaaaaa aaaaaa - * bbbbbbb|b -(cursor_left)-> bbbbbb|bb - * cccc cccc - */ - /** - * Handles cursor movement events in 2D text. - * @param direction - The direction to move the cursor - */ - handleMoveCursorEvent(direction: inputMethod.Direction): void { - switch (direction) { - case inputMethod.Direction.CURSOR_LEFT: - case inputMethod.Direction.CURSOR_RIGHT: - case inputMethod.Direction.CURSOR_UP: - case inputMethod.Direction.CURSOR_DOWN: - // simulate the hardware-keyboard arrow-key pressed with any directions, - // and the cursor index in text cache will be moved - this.simulateHardwareCursorMovement(direction, false); - break; - default: - Log.w(TAG, `"Unknown cursor movement direction: ${direction}"`); - break; - } - } - - /** - * text-selection changed with cursor moving at 2D-text - * (...) -> denotes the selection range of text - * aaaaaa aaaa(aa - * bbbb|bbbb -(cursor_up)-> bbbb)bbbb - * cccc cccc - * - * aaaaaa aaaaaa - * bbbbbbb|b -(cursor_down)-> bbbbbbb(b - * cccc cccc) - * - * aaaaaa aaaaaa - * bb|bbbbbb -(cursor_right)-> bb(b)bbbbb - * cccc cccc - * - * aaaaaa aaaaaa - * bbbbbbb|b -(cursor_left)-> bbbbbb(b)b - * cccc cccc - */ - /** - * Handles text selection changes with cursor movement. - * @param movement - The movement that triggers the selection change - */ - handleSelectByMovementEvent(movement: inputMethod.Movement) : void { - switch (movement.direction) { - case inputMethod.Direction.CURSOR_LEFT: - case inputMethod.Direction.CURSOR_RIGHT: - case inputMethod.Direction.CURSOR_UP: - case inputMethod.Direction.CURSOR_DOWN: - // simulate the hardware-keyboard shift-key combined with arrow-key simultaneously pressed, - // and the text selection changed with cursor movement - this.simulateHardwareCursorMovement(movement.direction, true); - break; - default: - Log.w(TAG, `"Unknown cursor movement direction: ${movement.direction}"`); - break; - } - } - - /** - * Gets the text to the left of the cursor. - * @param length - The maximum number of characters to return - * @returns The text to the left of the cursor - */ - getLeftTextOfCursor(length: number) : string { - if (length <= 0) { - return ""; - } - const strCacheLen = this.getStringCache().length; - if (!strCacheLen) { - return ""; - } - const cursorIdx = this.getSelectionStart(); - let startIdx = cursorIdx - length; - if (startIdx < 0) { - startIdx = 0; - } - return this.getStringCache().substring(startIdx, cursorIdx); - } - - /** - * Gets the text to the right of the cursor. - * @param length - The maximum number of characters to return - * @returns The text to the right of the cursor - */ - getRightTextOfCursor(length: number) : string { - if (length <= 0) { - return ""; - } - const strCacheLen = this.getStringCache().length; - if (!strCacheLen) { - return ""; - } - const cursorIdx = this.getSelectionEnd(); - let endIdx = cursorIdx + length; - if (endIdx >= strCacheLen) { - endIdx = strCacheLen; - } - return this.getStringCache().substring(cursorIdx, endIdx); - } - - /** - * Handles extended actions like select all, cut, copy, and paste. - * @param action - The action to perform - */ - handleExtendActionEvent(action: inputMethod.ExtendAction) : void { - switch (action) { - case inputMethod.ExtendAction.SELECT_ALL: - // select all text from the start to end, and notify cursor state updating - this.setSelectionStart(0); - this.setSelectionEnd(this.getStringCache().length); - this.notifyListenersIfNeeded(false, true, false); - break; - case inputMethod.ExtendAction.CUT: - // set to copy text before cutting - const cutText = this.getStringCache().substring(this.getSelectionStart(), this.getSelectionEnd()); - PasteboardUtils.setCopyData(cutText); - // based on the text selection length (endIdx - startIdx) for cutting - this.handleDeleteEvent(false, this.getSelectionEnd() - this.getSelectionStart(), false); - break; - case inputMethod.ExtendAction.COPY: - // obtain the current selection range of text cache as the copy text - const copyText = this.getStringCache().substring(this.getSelectionStart(), this.getSelectionEnd()); - PasteboardUtils.setCopyData(copyText); - break; - case inputMethod.ExtendAction.PASTE: - // PasteboardUtils.getPasteDataAsync() is a async function, so must await the async paste operatopn - // in sync func, otherwise there can be a problem of timing inconsistency in the insertion of pasted text here. - const handlePasteDataAsync = async () => { - try { - const pasteText = await PasteboardUtils.getPasteDataAsync(); - if (pasteText) { // insert the paste text into the editing box - this.handleInsertTextEvent(pasteText); - } - } catch (err) { - Log.e(TAG, "get PasteData error: " + err); - } - }; - handlePasteDataAsync(); - break; - default: - Log.w(TAG, `"Unknown ExtendAction: ${action}"`); - break; - } - } - - /** - * This method is used to simulate the hard-keyboard key event in soft-keyboard mode, - * Case 1: cursor moving is simulated by sending arrow-key event to flutter SDK (Dart-side), - * hence the cursor will move up/down/left/right in editing box. - * Case 2: text-selection changed with cursor moving is simulated by sending shift-key combined with arrow-key events - * to flutter SDK, hence the text-selection range will be updated by moving cursor up/down/left/right in editing box. - * @param direction: cursor move direction from IMC callback - * @param isShiftPressed: determine whether shift-key is pressed - */ - /** - * Simulates hardware keyboard cursor movement by sending key events to Flutter. - * @param direction - The direction to move the cursor - * @param isShiftPressed - Whether shift key is pressed (for text selection) - * @private - */ - private simulateHardwareCursorMovement(direction: inputMethod.Direction, isShiftPressed: boolean) : void { - // Here, '选择' button in '文本编辑' function of soft-keyboard inputMethod apps is a simulation for hardware shift-key. - // Therefore, when '选择' button is pressed in soft-keyboard equals to shift-key pressed in hard-keyboard - enum SimulatedKeyText { - KEY_SHIFT = 'KEYCODE_SHIFT_LEFT', - KEY_ARROW_UP = 'KEYCODE_DPAD_UP', - KEY_ARROW_DOWN = 'KEYCODE_DPAD_DOWN', - KEY_ARROW_LEFT = 'KEYCODE_DPAD_LEFT', - KEY_ARROW_RIGHT = 'KEYCODE_DPAD_RIGHT', - KEY_UNKNOWN = 'KEYCODE_UNKNOWN' - } - if (isShiftPressed) { // simulate shift-key down - this.sendSimulatedKeyEvent(new SimulateKeyEvent(KeyCode.KEYCODE_SHIFT_LEFT, SimulatedKeyText.KEY_SHIFT), false); - } - let arrowEvent: SimulateKeyEvent; - switch (direction) { - case inputMethod.Direction.CURSOR_UP: // simulate hardware arrow-up key event - arrowEvent = new SimulateKeyEvent(KeyCode.KEYCODE_DPAD_UP, SimulatedKeyText.KEY_ARROW_UP); - break; - case inputMethod.Direction.CURSOR_DOWN: // simulate hardware arrow-down key event - arrowEvent = new SimulateKeyEvent(KeyCode.KEYCODE_DPAD_DOWN, SimulatedKeyText.KEY_ARROW_DOWN); - break; - case inputMethod.Direction.CURSOR_LEFT: // simulate hardware arrow-left key event - arrowEvent = new SimulateKeyEvent(KeyCode.KEYCODE_DPAD_LEFT, SimulatedKeyText.KEY_ARROW_LEFT); - break; - case inputMethod.Direction.CURSOR_RIGHT: // simulate hardware arrow-right key event - arrowEvent = new SimulateKeyEvent(KeyCode.KEYCODE_DPAD_RIGHT, SimulatedKeyText.KEY_ARROW_RIGHT); - break; - default: - arrowEvent = new SimulateKeyEvent(KeyCode.KEYCODE_UNKNOWN, SimulatedKeyText.KEY_UNKNOWN); - break; - } - this.sendSimulatedKeyEvent(arrowEvent, false); // simulate arrow-key down - this.sendSimulatedKeyEvent(arrowEvent, true); // simulate arrow-key up - if (isShiftPressed) { // simulate shift-key up - this.sendSimulatedKeyEvent(new SimulateKeyEvent(KeyCode.KEYCODE_SHIFT_LEFT, SimulatedKeyText.KEY_SHIFT), true); - } - } - - /** - * Sends a simulated key event to Flutter. - * @param event - The key event to simulate - * @param isKeyUp - True for key up event, false for key down event - * @private - */ - private sendSimulatedKeyEvent(event: SimulateKeyEvent, isKeyUp: boolean) : void { - this.keyEventChannel?.simulateSendFlutterKeyEvent( - event, isKeyUp, { - onFrameworkResponse: (isEventHandled: boolean): void => {} - }); - } -} - -/** - * Interface for objects that watch for editing state changes. - * Changing the editing state in a didChangeEditingState callback may cause unexpected behavior. - */ -export interface EditingStateWatcher { - /** - * Called when the editing state changes. - * @param textChanged - Whether the text has changed - * @param selectionChanged - Whether the selection has changed - * @param composingRegionChanged - Whether the composing region has changed - */ - didChangeEditingState(textChanged: boolean, selectionChanged: boolean, composingRegionChanged: boolean): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextEditingDelta.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextEditingDelta.ets deleted file mode 100644 index 3ca1533..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextEditingDelta.ets +++ /dev/null @@ -1,117 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on TextEditingDelta.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import Log from '../../util/Log'; - -/** - * Represents a delta (change) in text editing state. - * This class tracks changes to text, selection, and composing regions. - */ -export class TextEditingDelta { - private static TAG = "TextEditingDelta"; - private oldText: string = ""; - private deltaText: string = ""; - private deltaStart: number = 0; - private deltaEnd: number = 0; - private newSelectionStart: number; - private newSelectionEnd: number; - private newComposingStart: number; - private newComposingEnd: number; - - /** - * Constructs a new TextEditingDelta instance. - * @param oldEditable - The text before the change - * @param selectionStart - The new selection start position - * @param selectionEnd - The new selection end position - * @param composingStart - The new composing region start position - * @param composingEnd - The new composing region end position - * @param replacementDestinationStart - Optional start position of the replacement destination - * @param replacementDestinationEnd - Optional end position of the replacement destination - * @param replacementSource - Optional replacement text - */ - constructor(oldEditable: string, - selectionStart: number, - selectionEnd: number, - composingStart: number, - composingEnd: number, - replacementDestinationStart?: number, - replacementDestinationEnd?: number, - replacementSource?: string) { - this.newSelectionStart = selectionStart; - this.newSelectionEnd = selectionEnd; - this.newComposingStart = composingStart; - this.newComposingEnd = composingEnd; - if (replacementDestinationStart === undefined || - replacementDestinationEnd === undefined || - replacementSource === undefined) { - this.setDeltas(oldEditable, "", -1, -1); - } else { - this.setDeltas( - oldEditable, - replacementSource, - replacementDestinationStart, - replacementDestinationEnd); - } - } - - /** - * Sets the delta information for this change. - * @param oldText - The text before the change - * @param newText - The replacement text - * @param newStart - The start position of the replacement - * @param newExtent - The end position of the replacement - */ - setDeltas(oldText: string, newText: string, newStart: number, newExtent: number): void { - this.oldText = oldText; - this.deltaText = newText; - this.deltaStart = newStart; - this.deltaEnd = newExtent; - } - - /** - * Converts this delta to a JSON representation. - * @returns A JSON object containing all delta information - */ - toJSON(): TextEditingDeltaJson { - let state: TextEditingDeltaJson = { - oldText: this.oldText.toString(), - deltaText: this.deltaText.toString(), - deltaStart: this.deltaStart, - deltaEnd: this.deltaEnd, - selectionBase: this.newSelectionStart, - selectionExtent: this.newSelectionEnd, - composingBase: this.newComposingStart, - composingExtent: this.newComposingEnd, - }; - return state; - } -} - -/** - * JSON representation of a text editing delta. - */ -export interface TextEditingDeltaJson { - /** The text before the change */ - oldText: string; - /** The replacement text */ - deltaText: string; - /** The start position of the replacement */ - deltaStart: number; - /** The end position of the replacement */ - deltaEnd: number; - /** The new selection start position */ - selectionBase: number; - /** The new selection end position */ - selectionExtent: number; - /** The new composing region start position */ - composingBase: number; - /** The new composing region end position */ - composingExtent: number; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextInputPlugin.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextInputPlugin.ets deleted file mode 100644 index 664800a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextInputPlugin.ets +++ /dev/null @@ -1,941 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on TextInputPlugin.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import TextInputChannel, { - Configuration, - TextEditState, - TextInputMethodHandler -} from '../../embedding/engine/systemchannels/TextInputChannel'; -import inputMethod from '@ohos.inputMethod'; -import Log from '../../util/Log'; -import { EditingStateWatcher, ListenableEditingState } from './ListenableEditingState'; -import Any from '../common/Any'; -import { inputDevice } from '@kit.InputKit'; -import { BusinessError } from '@kit.BasicServicesKit'; -import { PointerDeviceKind } from '../../embedding/ohos/OhosTouchProcessor'; -import deviceInfo from '@ohos.deviceInfo'; -import KeyEventChannel from '../../embedding/engine/systemchannels/KeyEventChannel'; -import { appManager, bundleManager } from '@kit.AbilityKit'; - -const INPUT_SUPPORT_API = 15; -const PREIVEW_TEXT_SUPPORT_API = 17; -const sdkApiVersion: number = deviceInfo.sdkApiVersion; - -/** - * Plugin for handling text input in Flutter applications. - * This class manages the interaction between Flutter's text input system - * and the OpenHarmony input method framework. - */ -export default class TextInputPlugin implements EditingStateWatcher { - private static TAG = "TextInputPlugin"; - private textInputChannel: TextInputChannel; - private keyEventChannel: KeyEventChannel | undefined; - private mTextInputHandler: TextInputMethodHandlerImpl; - - /** - * Constructs a new TextInputPlugin instance. - * @param textInputChannel - The TextInputChannel for communication with Flutter - * @param viewId - The view ID for focus management - * @param keyEventChannel - Optional KeyEventChannel for key event handling - */ - constructor(textInputChannel: TextInputChannel, viewId: string, keyEventChannel?: KeyEventChannel) { - this.textInputChannel = textInputChannel; - this.keyEventChannel = keyEventChannel; - // viewId is used for requestFocus - this.mTextInputHandler = new TextInputMethodHandlerImpl(this, viewId); - this.textInputChannel.setTextInputMethodHandler(this.mTextInputHandler); - } - - /** - * Clears the current text input client. - */ - public clearTextInputClient() { - this.textInputChannel.textInputMethodHandler?.clearClient(); - } - - /** - * Sets the text input editing state. - * @param state - The new editing state - */ - setTextInputEditingState(state: TextEditState) { - - } - - /** - * Gets the current editing state. - * @returns The current ListenableEditingState instance - */ - getEditingState() { - return this.mTextInputHandler.mEditable; - } - - /** - * Handles changes to preview text editing state. - * This method updates the editing widget with preview text from the input method. - * @param inputTarget - The input target - * @param editable - The editable state containing preview text - */ - didChangePreviewTextEditingState(inputTarget: InputTarget, editable: ListenableEditingState): void { - let stringCache: string = editable.getStringCache(); - // obtain the start index of editing preview text - let startIdx: number = editable.getLeftIdxOfPreviewTextRange(); - let leftStringCache = stringCache.substring(0, startIdx); - let rightStringCache = stringCache.substring(startIdx, stringCache.length); - // concat the string cache and preview text and show on the TextField - let finalStringCache = leftStringCache + editable.getPreviewText() + rightStringCache; - // update the selection range - let newSelectionStart: number = startIdx + editable.getPreviewText().length; - let newSelectionEnd: number = startIdx + editable.getPreviewText().length; - - this.textInputChannel.updateEditingState(inputTarget.id, finalStringCache, - newSelectionStart, newSelectionEnd, - editable.getComposingStart(), editable.getComposingEnd()) - // notify current cursor index to the InputMethodFramework - this.mTextInputHandler.inputMethodController.changeSelection(finalStringCache, - newSelectionStart, newSelectionEnd); - } - - /** - * Called when the editing state changes. - * @param textChanged - Whether the text has changed - * @param selectionChanged - Whether the selection has changed - * @param composingRegionChanged - Whether the composing region has changed - */ - didChangeEditingState(textChanged: boolean, selectionChanged: boolean, composingRegionChanged: boolean): void { - let editable = this.mTextInputHandler.mEditable; - let inputTarget = this.mTextInputHandler.inputTarget; - let configuration = this.mTextInputHandler.configuration; - if (configuration != null && configuration.enableDeltaModel) { - this.textInputChannel.updateEditingStateWithDeltas(inputTarget.id, editable.extractBatchTextEditingDeltas()); - editable.clearBatchDeltas(); - } else if (sdkApiVersion >= PREIVEW_TEXT_SUPPORT_API && editable.getPreviewText() != "") { - this.didChangePreviewTextEditingState(inputTarget, editable); - } - else { - this.textInputChannel.updateEditingState(inputTarget.id, editable.getStringCache(), - editable.getSelectionStart(), editable.getSelectionEnd(), - editable.getComposingStart(), editable.getComposingEnd()) - } - } - - /** - * Detaches the text input plugin from the input method framework. - */ - detach(): void { - this.mTextInputHandler.inputMethodController.detach((err) => { - if (err) { - Log.e(TextInputPlugin.TAG, "Failed to detach: " + JSON.stringify(err)); - } - }) - } - - /** - * Destroys the text input plugin, hiding the keyboard and cleaning up resources. - */ - destroy() { - // Since the Dart side no longer listens to the lifecycle, - // the keyboard must be explicitly hidden before the engine is destroyed. - this.mTextInputHandler.hide(); - this.textInputChannel.setTextInputMethodHandler(null); - } -} - -const INPUT_TYPE_NAME = - ['NONE', 'TEXT', 'MULTILINE', 'NUMBER', 'PHONE', 'DATETIME', 'EMAIL_ADDRESS', 'URL', 'VISIBLE_PASSWORD'] - -/** - * Implementation of TextInputMethodHandler for OpenHarmony input method framework. - * This class handles all interactions with the system input method. - */ -class TextInputMethodHandlerImpl implements TextInputMethodHandler { - private static TAG = "TextInputMethodHandlerImpl"; - private textConfig: inputMethod.TextConfig; - /** The input method controller for managing the keyboard. */ - inputMethodController: inputMethod.InputMethodController; - /** The current input target. */ - inputTarget: InputTarget; - /** The current text input configuration, or null if not set. */ - public configuration: Configuration | null = null; - private lastKind?: PointerDeviceKind; - /** The editable state for text input. */ - mEditable: ListenableEditingState; - private mRestartInputPending: boolean = false; - private plugin: EditingStateWatcher | Any; - private imcFlag: boolean = false; - private keyboardStatus: inputMethod.KeyboardStatus = inputMethod.KeyboardStatus.HIDE; - private inputAttribute: inputMethod.InputAttribute = - { textInputType: inputMethod.TextInputType.TEXT, enterKeyType: inputMethod.EnterKeyType.NONE }; - private keyboardFocusState: boolean = false; - private focusViewId: string = ""; - // Record the type of soft keyboard that was triggered last time - private lastInputType?: inputMethod.TextInputType; - private isInputMethodAttached: boolean = false; - - /** - * Constructs a new TextInputMethodHandlerImpl instance. - * @param plugin - The TextInputPlugin instance - * @param viewId - The view ID for focus management - */ - constructor(plugin: TextInputPlugin | Any, viewId: string) { - this.textConfig = { - inputAttribute: this.inputAttribute - }; - this.plugin = plugin; - this.mEditable = new ListenableEditingState(null, 0); - this.inputMethodController = inputMethod.getController(); - this.inputTarget = new InputTarget(Type.NO_TARGET, 0); - this.focusViewId = viewId; - } - - /** - * Shows the text input keyboard if appropriate. - * The keyboard is only shown if the input type is not NONE. - */ - show(): void { - try { - let isPhysicalKeyboard = false; - inputDevice.getDeviceList((Error: Error, ids: Array) => { - for (let i = 0; i < ids.length; i++) { - const type = inputDevice.getKeyboardTypeSync(ids[i]); - if (type === inputDevice.KeyboardType.ALPHABETIC_KEYBOARD || - type === inputDevice.KeyboardType.DIGITAL_KEYBOARD) { - isPhysicalKeyboard = true; - break; - } - } - // 适配api20,在外接键盘状态下,阻止软键盘重复拉起 - if (isPhysicalKeyboard) { - this.keyboardStatus = inputMethod.KeyboardStatus.SHOW; - } - }) - } catch (error) { - Log.e(TextInputMethodHandlerImpl.TAG, - `Show function failed to query device. Code is ${error.code}, message is ${error.message}`) - } - if (this.canShowTextInput()) { - // Ensure the Xcomponent gains focus before the soft keyboard is displayed. - focusControl.requestFocus(this.focusViewId); - this.keyboardFocusState = true; - this.showTextInput(); - } else { - this.hide(); - } - } - - /** - * Hides the text input keyboard. - */ - hide(): void { - // Ensure the Xcomponent loses focus before the soft keyboard is hided. - focusControl.requestFocus("unfocus-xcomponent-node"); - this.keyboardFocusState = false; - this.hideTextInput(); - } - - /** - * Gets the current keyboard focus state. - * @returns True if the keyboard has focus, false otherwise - */ - getKeyboardFocusState() { - return this.keyboardFocusState; - } - - /** - * Requests autofill functionality. - */ - requestAutofill(): void { - - } - - /** - * Finishes the autofill context. - * @param shouldSave - Whether to save the autofill data - */ - finishAutofillContext(shouldSave: boolean): void { - - } - - /** - * Sets the text input client. - * @param textInputClientId - The client ID - * @param configuration - The input configuration - */ - setClient(textInputClientId: number, configuration: Configuration | null): void { - this.setTextInputClient(textInputClientId, configuration); - } - - /** - * Updates the input configuration. - * @param configuration - The new input configuration - */ - updateConfig(configuration: Configuration | null) { - if (configuration) { - this.lastKind = this.configuration?.deviceKind; - this.configuration = configuration; - if (configuration.inputType) { - this.textConfig.inputAttribute.textInputType = configuration.inputType.type; - this.textConfig.inputAttribute.enterKeyType = configuration.inputAction as Any; - } - } - } - - /** - * Sets the platform view client for text input. - * @param id - The platform view ID - * @param usesVirtualDisplay - Whether the platform view uses a virtual display - */ - setPlatformViewClient(id: number, usesVirtualDisplay: boolean): void { - - } - - /** - * Sets the editable size and transform. - * @param width - The width of the editable area - * @param height - The height of the editable area - * @param transform - The transformation matrix - */ - setEditableSizeAndTransform(width: number, height: number, transform: number[]): void { - - } - - /** - * Sets the cursor size and position. - * @param cursorInfo - The cursor information - */ - setCursorSizeAndPosition(cursorInfo: inputMethod.CursorInfo) { - try { - this.inputMethodController.updateCursor(cursorInfo, (err: BusinessError) => { - if (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to updateCursor:" + JSON.stringify(err)); - return; - } - }) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to updateCursor:" + JSON.stringify(err)); - } - } - - /** - * Sets the editing state from Flutter. - * @param editingState - The new editing state - */ - setEditingState(editingState: TextEditState): void { - Log.d(TextInputMethodHandlerImpl.TAG, - "text:" + editingState.text + " selectionStart:" + editingState.selectionStart + " selectionEnd:" - + editingState.selectionEnd + " composingStart:" + editingState.composingStart + " composingEnd" + - editingState.composingEnd); - this.mEditable.setPreviewText(""); - this.mEditable.setIsCursorIdxOutOfPreviewTextRange(false); - this.mEditable.updateTextInputState(editingState); - // notify current cursor index or selection range to the InputMethodFramework - this.inputMethodController.changeSelection("", - -1, -1, (err: BusinessError) => { - if (err) { - Log.e(TextInputMethodHandlerImpl.TAG, `Failed to changeSelection, code: ${err.code}, message: ${err.message}`); - return; - } - this.inputMethodController.changeSelection(editingState.text, - editingState.selectionStart, editingState.selectionEnd); - }); - if (sdkApiVersion >= PREIVEW_TEXT_SUPPORT_API) { - this.previewTextChangeSelection(editingState); - } - } - - /** - * Handles selection changes when preview text is active. - * If the cursor moves outside the preview text range, the preview text is inserted. - * @param editingState - The current editing state - */ - previewTextChangeSelection(editingState: TextEditState): void { - // if users only modify the cursor index without typing new preview text, - // clear the current preview text cache for avoiding inserting redundant preview text - // to insert at the current cursor position - let currCursorIndex: number = editingState.selectionStart; - let leftIdxOfPreviewText: number = this.mEditable.getLeftIdxOfPreviewTextRange(); - let rightIdxOfPreviewText: number = this.mEditable.getRightIdxOfPreviewTextRange(); - - let isCursorIdxOutOfPreviewTextRange = (leftIdxOfPreviewText != rightIdxOfPreviewText) && - (currCursorIndex < leftIdxOfPreviewText || currCursorIndex > rightIdxOfPreviewText) - // if user move the current cursor index out of editing preview text range, - // immediately inserting the first candidate word into the editing widget - if (isCursorIdxOutOfPreviewTextRange) { - // change selection async callback - this.inputMethodController.changeSelection(editingState.text, leftIdxOfPreviewText, rightIdxOfPreviewText) - .then(() => { - // force the cursor position at the end of editing preview text - editingState.selectionStart = rightIdxOfPreviewText; - editingState.selectionEnd = rightIdxOfPreviewText; - // update TextInputState and reset the preview text state - this.mEditable.updateTextInputState(editingState); - this.mEditable.setPreviewText(""); - }) - .catch((err: BusinessError) => { - console.error(`Failed to previewTextChangeSelection: ${JSON.stringify(err)}`); - }) - } - } - - /** - * Clears the current text input client. - */ - clearClient(): void { - this.clearTextInputClient(); - } - - private async showTextInput(): Promise { - if (!this.lastInputType) { - this.setLastInputType(this.configuration?.inputType?.type); - } - if (this.lastKind === undefined && this.configuration?.deviceKind !== undefined) { - this.lastKind = this.configuration.deviceKind; - } - if (this.keyboardStatus == inputMethod.KeyboardStatus.SHOW) { - // 增加键盘拉起状态时也调用attach,参数false则attach不会拉起键盘 - await this.attach(false); - if (this.lastKind != this.configuration?.deviceKind || - this.hasSecureKeyboardInSwitch()) { - if (deviceInfo.sdkApiVersion >= INPUT_SUPPORT_API) { - await this.inputMethodController.showTextInput(this.getRequestReason()); - } else { - await this.inputMethodController.showTextInput(); - } - if (this.configuration?.deviceKind !== undefined) { - this.lastKind = this.configuration.deviceKind; - } - } - this.setLastInputType(this.configuration?.inputType?.type); - return; - } - this.setLastInputType(this.configuration?.inputType?.type); - await this.attach(true); - if (this.configuration?.deviceKind !== undefined) { - this.lastKind = this.configuration.deviceKind; - } - if (!this.imcFlag) { - this.listenKeyBoardEvent(); - } - } - - private async hideTextInput(): Promise { - await this.inputMethodController.detach().then(() => { - this.keyboardStatus = inputMethod.KeyboardStatus.HIDE; - this.cancelListenKeyBoardEvent(); - }).catch((err: BusinessError) => { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to detach: " + JSON.stringify(err)); - this.keyboardStatus = inputMethod.KeyboardStatus.NONE; - }); - } - - /** - * Attaches to the input method controller. - * @param showKeyboard - Whether to show the keyboard immediately - * @private - */ - async attach(showKeyboard: boolean): Promise { - try { - // must register previewtext callbacks before attachment - if (sdkApiVersion >= PREIVEW_TEXT_SUPPORT_API) { - this.registerPreviewTextCallbacks(); - } - if (deviceInfo.sdkApiVersion >= INPUT_SUPPORT_API) { - await this.inputMethodController.attach(showKeyboard, this.textConfig, this.getRequestReason()).then(async () => { - await this.handleAttach(showKeyboard); - }); - } else { - await this.inputMethodController.attach(showKeyboard, this.textConfig).then(async () => { - await this.handleAttach(showKeyboard); - }); - } - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to attach:" + JSON.stringify(err)); - this.keyboardStatus = inputMethod.KeyboardStatus.NONE; - } - } - - private async handleAttach(showKeyboard: boolean): Promise { - let isDetached = false; - if (showKeyboard) { - isDetached = await this.detachIfBackground(); - } - if (isDetached) { - this.isInputMethodAttached = false; - this.keyboardStatus = inputMethod.KeyboardStatus.NONE; - } else { - this.isInputMethodAttached = true; - this.cacheMethodCall(); - this.keyboardStatus = inputMethod.KeyboardStatus.SHOW; - } - } - - private async detachIfBackground(): Promise { - try { - const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; - const bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags); - const currentInfo = await appManager.getRunningProcessInformation(); - const targetBundleName = bundleInfo?.name; - const currentProcess = currentInfo.find(info => info.bundleNames?.includes(targetBundleName)); - //STATE_FOREGROUND 代表进程处于前台,界面获焦并显示时状态为STATE_ACTIVE,参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-app-ability-appmanager#processstate10 - if ( - currentProcess && - (currentProcess.state === appManager.ProcessState.STATE_FOREGROUND || - currentProcess.state === appManager.ProcessState.STATE_BACKGROUND) - ) { - await this.inputMethodController.hideTextInput(); - return true; - } - } catch (err) { - Log.e( - TextInputMethodHandlerImpl.TAG, - `detachIfBackground error: ${JSON.stringify(err)}`, - ); - } - return false; - } - - cacheMethodCall(): void { - // Cache method calls when input method is attached; no-op if not used by this version - } - - /** - * Gets the request reason for showing the keyboard based on the input device kind. - * @returns The request reason for keyboard display - */ - getRequestReason() : inputMethod.RequestKeyboardReason { - let deviceKind : PointerDeviceKind = this.configuration?.deviceKind ?? PointerDeviceKind.UNKNOWN - Log.i(TextInputMethodHandlerImpl.TAG, "getRequestReason: deviceKind=" + deviceKind); - switch (deviceKind) { - case PointerDeviceKind.TOUCH: - return inputMethod.RequestKeyboardReason.TOUCH; - case PointerDeviceKind.MOUSE: - return inputMethod.RequestKeyboardReason.MOUSE; - case PointerDeviceKind.STYLUS: - case PointerDeviceKind.INVERTED_STYLUS: - case PointerDeviceKind.TRACKPAD: - return inputMethod.RequestKeyboardReason.OTHER; - default: - return inputMethod.RequestKeyboardReason.NONE; - } - } - - /** - * Handles focus state changes. - * @param focusState - The new focus state - */ - handleChangeFocus(focusState: boolean) { - if (focusState && this.keyboardFocusState) { - // When the app loses focus, the system automatically detaches the input method. - // Upon regaining focus, if the input method should be displayed, it must be reattached. - this.show(); - } - try { - inputDevice.getDeviceList((Error: Error, ids: Array) => { - let isPhysicalKeyboard = false; - for (let i = 0; i < ids.length; i++) { - const type = inputDevice.getKeyboardTypeSync(ids[i]); - if (type == inputDevice.KeyboardType.ALPHABETIC_KEYBOARD || type == inputDevice.KeyboardType.DIGITAL_KEYBOARD) { - isPhysicalKeyboard = true; - break; - } - } - - if(focusState && isPhysicalKeyboard && this.keyboardFocusState) { - this.cancelListenKeyBoardEvent(); - this.inputMethodController.detach().then(async () =>{ - await this.attach(true); - this.listenKeyBoardEvent(); - }) - } - }) - } catch (error) { - Log.e(TextInputMethodHandlerImpl.TAG, `Failed to query device. Code is ${error.code}, message is ${error.message}`) - } - } - - /** - * Updates the input attribute configuration. - */ - async updateAttribute(): Promise { - if (this.keyboardStatus != inputMethod.KeyboardStatus.SHOW) { - return; - } - try { - await this.inputMethodController.updateAttribute(this.inputAttribute); - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to updateAttribute:" + JSON.stringify(err)); - } - } - - /** - * Sets the text input client with the given configuration. - * @param client - The client ID - * @param configuration - The input configuration - */ - setTextInputClient(client: number, configuration: Configuration | null): void { - if (configuration) { - this.lastKind = this.configuration?.deviceKind; - this.configuration = configuration; - if (configuration.inputType) { - this.textConfig.inputAttribute.textInputType = configuration.inputType.type; - this.textConfig.inputAttribute.enterKeyType = configuration.inputAction as Any; - } - } - if (this.canShowTextInput()) { - this.inputTarget = new InputTarget(Type.FRAMEWORK_CLIENT, client); - } else { - this.inputTarget = new InputTarget(Type.NO_TARGET, client); - } - this.mEditable.removeEditingStateListener(this.plugin); - - this.mEditable = new ListenableEditingState( - this.plugin.textInputChannel, this.inputTarget.id, this.plugin.keyEventChannel); - - this.mRestartInputPending = true; - this.mEditable.addEditingStateListener(this.plugin); - - this.inputAttribute = this.textConfig.inputAttribute; - - this.updateAttribute(); - } - - setLastInputType(inputType?: inputMethod.TextInputType): void { - this.lastInputType = inputType; - } - - // It is used to determine whether there is a safe keyboard for the keyboard type - // awakened by the two input boxes when a soft keyboard is already suspended - // and switching input boxes - hasSecureKeyboardInSwitch(): boolean { - // Since this method is called in showTextInput to determine whether a secure - // keyboard needs to be pulled up, the parameter must be true - this.handleAttach(true); - return this.lastInputType === inputMethod.TextInputType.VISIBLE_PASSWORD || - this.configuration?.inputType?.type === inputMethod.TextInputType.VISIBLE_PASSWORD; - } - - /** - * Checks if text input can be shown. - * @returns True if text input can be shown, false if input type is NONE - */ - canShowTextInput(): boolean { - if (this.configuration == null || this.configuration.inputType == null) { - return true; - } - return this.configuration.inputType.type != inputMethod.TextInputType.NONE; - } - - /** - * Registers callbacks for preview text functionality. - */ - registerPreviewTextCallbacks(): void { - try { - this.inputMethodController.on("setPreviewText", this.setPreviewTextCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe setPreviewText:" + JSON.stringify(err)); - this.unregisterPreviewTextCallbacks(); - return; - } - - try { - this.inputMethodController.on("finishTextPreview", this.finishPreviewTextCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe finishTextPreview:" + JSON.stringify(err)); - this.unregisterPreviewTextCallbacks(); - return; - } - } - - /** - * Unregisters preview text callbacks. - */ - unregisterPreviewTextCallbacks(): void { - this.inputMethodController?.off("setPreviewText", this.setPreviewTextCallback) - this.inputMethodController?.off("finishTextPreview", this.finishPreviewTextCallback); - } - - /** - * Registers listeners for keyboard events from the input method framework. - */ - listenKeyBoardEvent(): void { - try { - this.inputMethodController.on('insertText', this.insertTextCallback); - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe insertText:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('deleteLeft', this.deleteLeftCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe deleteLeft:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('deleteRight', this.deleteRightCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe deleteRight:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('sendFunctionKey', this.sendFunctionKeyCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe sendFunctionKey:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('sendKeyboardStatus', this.sendKeyboardStatusCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe sendKeyboardStatus:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('selectByRange', this.selectByRangeCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe selectByRange:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('moveCursor', this.moveCursorCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe moveCursor:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - try { - this.inputMethodController.on('handleExtendAction', this.handleExtendActionCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe handleExtendAction:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - try { - this.inputMethodController.on('selectByMovement', this.selectByMovementCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe selectByMovement:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - try { - this.inputMethodController.on('getLeftTextOfCursor', this.getLeftTextOfCursorCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe getLeftTextOfCursor:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - try { - this.inputMethodController.on('getRightTextOfCursor', this.getRightTextOfCursorCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe getRightTextOfCursor:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - try { - this.inputMethodController.on('getTextIndexAtCursor', this.getTextIndexAtCursorCallback) - } catch (err) { - Log.e(TextInputMethodHandlerImpl.TAG, "Failed to subscribe getTextIndexAtCursor:" + JSON.stringify(err)); - this.cancelListenKeyBoardEvent(); - return; - } - - Log.d(TextInputMethodHandlerImpl.TAG, "listenKeyBoardEvent success"); - this.imcFlag = true; - } - - private setPreviewTextCallback = (text: string, range: inputMethod.Range) => { - Log.i(TextInputMethodHandlerImpl.TAG, - "setPreviewTextCallback: text = " + text + " range = " + JSON.stringify(range)); - this.mEditable.handleInsertPreviewTextEvent(text, range); - } - - private finishPreviewTextCallback = () => { - Log.i(TextInputMethodHandlerImpl.TAG, "finishPreviewTextCallback"); - // When editing preview text, meanwhile switch the app to the background and - // the preview text will automatically insert into the string cache of TextField - this.mEditable.handleInsertTextEvent(this.mEditable.getPreviewText()); - this.mEditable.clearPreviewTextContents(); - } - - private insertTextCallback = (text: string) => { - this.mEditable.handleInsertTextEvent(text); - // notify the current cursor index to InputMethodFramework for preview mode - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - private deleteLeftCallback = (length: number) => { - this.mEditable.handleDeleteEvent(false, length, this.configuration?.enableDeltaModel); - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - private deleteRightCallback = (length: number) => { - this.mEditable.handleDeleteEvent(true, length, this.configuration?.enableDeltaModel); - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - private sendFunctionKeyCallback = (functionKey: inputMethod.FunctionKey) => { - if (functionKey.enterKeyType == inputMethod.EnterKeyType.NEWLINE) { - // insertText回调不会通知换行事件,需要在这里进行处理 - this.mEditable.handleNewlineEvent(); - } - this.mEditable.handleFunctionKey(functionKey); - } - - private sendKeyboardStatusCallback = (state: inputMethod.KeyboardStatus) => { - // api20开始,外接键盘状态下,点击输入框会拉起软键盘,此时要阻止onConnectionClosed,否则输入框会无法输入 - try { - let isPhysicalKeyboard = false; - inputDevice.getDeviceList((Error: Error, ids: Array) => { - for (let i = 0; i < ids.length; i++) { - const type = inputDevice.getKeyboardTypeSync(ids[i]); - if (type === inputDevice.KeyboardType.ALPHABETIC_KEYBOARD || - type === inputDevice.KeyboardType.DIGITAL_KEYBOARD) { - isPhysicalKeyboard = true; - return; - } - } - this.keyboardStatus = state; - if (state === inputMethod.KeyboardStatus.HIDE) { - this.plugin.textInputChannel.onConnectionClosed(this.inputTarget.id); - } - }) - } catch (error) { - Log.e(TextInputMethodHandlerImpl.TAG, - `SendKeyboardStatusCallback function failed to query device. Code is ${error.code}, message is ${error.message}`) - } - } - - // obtain the range to update cursor idx to start/end of text cache - private selectByRangeCallback = (range: inputMethod.Range) => { - this.mEditable.handleSelectByRangeEvent(range); - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - // move cursor postion with left, right, up and down in editing widget - private moveCursorCallback = (direction: inputMethod.Direction) => { - this.mEditable.handleMoveCursorEvent(direction); - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - // handle extend clipboard actions, like 'select all', 'cut', 'copy' and 'paste' - private handleExtendActionCallback = (action: inputMethod.ExtendAction) => { - this.mEditable.handleExtendActionEvent(action); - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - // text selection range changed with cursor direction movement - private selectByMovementCallback = (movement: inputMethod.Movement) => { - this.mEditable.handleSelectByMovementEvent(movement); - this.inputMethodController.changeSelection(this.mEditable.getStringCache(), this.mEditable.getSelectionStart(), - this.mEditable.getSelectionEnd()); - } - - // return the left-side text string of current cursor index ("abc|345" -> return "abc") - private getLeftTextOfCursorCallback = (length: number) : string => { - const retText = this.mEditable.getLeftTextOfCursor(length); - Log.i(TextInputMethodHandlerImpl.TAG, "getLeftTextOfCursor: " + retText); - return retText; - } - - // return the right-side text string of current cursor index ("abc|345" -> return "345") - private getRightTextOfCursorCallback = (length: number) : string => { - const retText = this.mEditable.getRightTextOfCursor(length); - Log.i(TextInputMethodHandlerImpl.TAG, "getRightTextOfCursor: " + retText); - return retText; - } - - // return the current cursor index of editing text - private getTextIndexAtCursorCallback = () => { - const cursorIdx = this.mEditable.getSelectionStart(); - Log.i(TextInputMethodHandlerImpl.TAG, "getTextIndexAtCursor: " + cursorIdx); - return cursorIdx; - } - - /** - * Cancels all keyboard event listeners. - */ - cancelListenKeyBoardEvent(): void { - this.inputMethodController?.off('insertText', this.insertTextCallback); - this.inputMethodController?.off('deleteLeft', this.deleteLeftCallback); - this.inputMethodController?.off('deleteRight', this.deleteRightCallback); - this.inputMethodController?.off('sendFunctionKey', this.sendFunctionKeyCallback); - this.inputMethodController?.off('sendKeyboardStatus', this.sendKeyboardStatusCallback); - this.inputMethodController?.off('selectByRange', this.selectByRangeCallback); - this.inputMethodController?.off('moveCursor', this.moveCursorCallback); - this.inputMethodController?.off('handleExtendAction', this.handleExtendActionCallback); - this.inputMethodController?.off('selectByMovement', this.selectByMovementCallback); - this.inputMethodController?.off('getLeftTextOfCursor', this.getLeftTextOfCursorCallback); - this.inputMethodController?.off('getRightTextOfCursor', this.getRightTextOfCursorCallback); - this.inputMethodController?.off('getTextIndexAtCursor', this.getTextIndexAtCursorCallback); - this.imcFlag = false; - } - - /** - * Clears the text input client. - */ - public clearTextInputClient(): void { - if (this.inputTarget.type == Type.VIRTUAL_DISPLAY_PLATFORM_VIEW) { - return; - } - this.mEditable.removeEditingStateListener(this.plugin); - this.configuration = null; - this.inputTarget = new InputTarget(Type.NO_TARGET, 0); - } -} - -/** - * Enumeration of input target types. - */ -enum Type { - /** No input target */ - NO_TARGET, - /** InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter framework. */ - FRAMEWORK_CLIENT, - /** InputConnection is managed by a platform view that is presented on a virtual display. */ - VIRTUAL_DISPLAY_PLATFORM_VIEW, - /** InputConnection is managed by a platform view that is presented on a physical display. */ - PHYSICAL_DISPLAY_PLATFORM_VIEW, -} - -/** - * Represents a target for text input operations. - */ -export class InputTarget { - /** The type of input target. */ - type: Type; - /** The unique identifier for this input target. */ - id: number; - - /** - * Constructs a new InputTarget instance. - * @param type - The type of input target - * @param id - The target ID - */ - constructor(type: Type, id: number) { - this.type = type; - this.id = id; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextUtils.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextUtils.ets deleted file mode 100644 index 3e0ae4c..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/editing/TextUtils.ets +++ /dev/null @@ -1,440 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterTextUtils.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import FlutterNapi from '../../embedding/engine/FlutterNapi'; -import Log from '../../util/Log'; - -const LINE_FEED: number = 0x0A; -const CARRIAGE_RETURN: number = 0x0D; -const COMBINING_ENCLOSING_KEYCAP: number = 0x20E3; -const CANCEL_TAG: number = 0xE007F; -const ZERO_WIDTH_JOINER: number = 0x200D; - -const TAG = "TextUtils"; - -/** - * Utility class for text processing operations, including Unicode code point handling, - * emoji detection, and text offset calculations. - */ -export class FlutterTextUtils { - - /** - * Checks if a Unicode code point represents an emoji. - * @param code - The Unicode code point to check - * @returns True if the code point is an emoji, false otherwise - */ - static isEmoji(code: number): boolean { - return FlutterNapi.unicodeIsEmoji(code); - } - - /** - * Checks if a Unicode code point represents an emoji modifier. - * @param code - The Unicode code point to check - * @returns True if the code point is an emoji modifier, false otherwise - */ - static isEmojiModifier(code: number): boolean { - return FlutterNapi.unicodeIsEmojiModifier(code); - } - - /** - * Checks if a Unicode code point represents an emoji modifier base. - * @param code - The Unicode code point to check - * @returns True if the code point is an emoji modifier base, false otherwise - */ - static isEmojiModifierBase(code: number): boolean { - return FlutterNapi.unicodeIsEmojiModifierBase(code); - } - - /** - * Checks if a Unicode code point represents a variation selector. - * @param code - The Unicode code point to check - * @returns True if the code point is a variation selector, false otherwise - */ - static isVariationSelector(code: number): boolean { - return FlutterNapi.unicodeIsVariationSelector(code); - } - - /** - * Checks if a Unicode code point represents a regional indicator symbol. - * @param code - The Unicode code point to check - * @returns True if the code point is a regional indicator symbol, false otherwise - */ - static isRegionalIndicatorSymbol(code: number): boolean { - return FlutterNapi.unicodeIsRegionalIndicatorSymbol(code); - } - - /** - * Checks if a Unicode code point is a tag specification character. - * @param code - The Unicode code point to check - * @returns True if the code point is a tag specification character, false otherwise - */ - static isTagSpecChar(code: number): boolean { - return 0xE0020 <= code && code <= 0xE007E; - } - - /** - * Checks if a Unicode code point is a keycap base character (0-9, #, *). - * @param code - The Unicode code point to check - * @returns True if the code point is a keycap base, false otherwise - */ - static isKeycapBase(code: number): boolean { - return ('0'.charCodeAt(0) <= code && code <= '9'.charCodeAt(0)) || code == '#'.charCodeAt(0) || code == '*'.charCodeAt(0); - } - - /** - * Gets the Unicode code point before the specified offset in the text. - * Handles surrogate pairs correctly. - * @param text - The text to examine - * @param offset - The offset position - * @returns The Unicode code point before the offset - * @throws RangeError if the offset is out of range - */ - static codePointBefore(text: string, offset: number): number { - if (offset <= 0 || offset > text.length) { - throw new RangeError('Offset out of range'); - } - - // Get the character before the offset - const char = text[offset - 1]; - - // Check if it is a low surrogate (part of a surrogate pair) - if (offset > 1 && char >= '\uDC00' && char <= '\uDFFF') { - const prevChar = text[offset - 2]; - // Check if the previous character is a high surrogate - if (prevChar >= '\uD800' && prevChar <= '\uDBFF') { - // If it is, combine the surrogate pair into a full Unicode code point - return (prevChar.charCodeAt(0) - 0xD800) * 0x400 + (char.charCodeAt(0) - 0xDC00) + 0x10000; - } - } - - // Return the code point of the single character (if it's not a surrogate pair) - return char.charCodeAt(0); - } - - /** - * Gets the Unicode code point at the specified offset in the text. - * Handles surrogate pairs correctly. - * @param text - The text to examine - * @param offset - The offset position - * @returns The Unicode code point at the offset - * @throws RangeError if the offset is out of range - */ - static codePointAt(text: string, offset: number): number { - if (offset >= text.length) { - throw new RangeError('Offset out of range'); - } - let char = text[offset]; - - // Check if it is a high surrogate (part of a surrogate pair) - if (char >= '\uD800' && char <= '\uDBFF' && offset + 1 < text.length) { - const nextChar = text[offset + 1]; - // Check if the previous character is a low surrogate - if (nextChar >= '\uDC00' && nextChar <= '\uDFFF') { - // If it is, combine the surrogate pair into a full Unicode code point - return (char.charCodeAt(0) - 0xD800) * 0x400 + (nextChar.charCodeAt(0) - 0xDC00) + 0x10000; - } - } - return char.charCodeAt(0); - } - - /** - * Gets the number of UTF-16 code units required to represent a Unicode code point. - * @param codePoint - The Unicode code point - * @returns 1 for BMP characters (0x0000-0xFFFF), 2 for supplementary characters (0x10000-0x10FFFF) - */ - static charCount(codePoint: number): number { - // If the code point is in the BMP range (0x0000 - 0xFFFF), it needs 1 UTF-16 code unit - if (codePoint <= 0xFFFF) { - return 1; - } - // If the code point is in the supplementary range (0x10000 - 0x10FFFF), it needs 2 UTF-16 code units - return 2; - } - - /** - * Gets the offset before the current position, handling complex Unicode sequences - * such as emojis, regional indicators, keycaps, and variation selectors. - * @param text - The text to examine - * @param offset - The current offset position - * @returns The offset before the current position, accounting for Unicode sequences - */ - static getOffsetBefore(text: string, offset: number): number { - if (offset <= 1) { - return 0; - } - - let codePoint: number = FlutterTextUtils.codePointBefore(text, offset); - let deleteCharCount: number = FlutterTextUtils.charCount(codePoint); - let lastOffset: number = offset - deleteCharCount; - - if (lastOffset == 0) { - return 0; - } - - // Line Feed - if (codePoint == LINE_FEED) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - if (codePoint == CARRIAGE_RETURN) { - ++deleteCharCount; - } - return offset - deleteCharCount; - } - - // Flags - if (FlutterTextUtils.isRegionalIndicatorSymbol(codePoint)) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - let regionalIndicatorSymbolCount: number = 1; - while (lastOffset > 0 && FlutterTextUtils.isRegionalIndicatorSymbol(codePoint)) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - regionalIndicatorSymbolCount++; - } - if (FlutterTextUtils.isRegionalIndicatorSymbol(codePoint)) { - regionalIndicatorSymbolCount++; - } - if (regionalIndicatorSymbolCount % 2 == 0) { - deleteCharCount += 2; - } - return offset - deleteCharCount; - } - - // Keycaps - if (codePoint == COMBINING_ENCLOSING_KEYCAP) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - if (lastOffset > 0 && FlutterTextUtils.isVariationSelector(codePoint)) { - let tmpCodePoint: number = FlutterTextUtils.codePointBefore(text, lastOffset); - if (FlutterTextUtils.isKeycapBase(tmpCodePoint)) { - deleteCharCount += FlutterTextUtils.charCount(codePoint) + FlutterTextUtils.charCount(tmpCodePoint); - } - } else if (FlutterTextUtils.isKeycapBase(codePoint)) { - deleteCharCount += FlutterTextUtils.charCount(codePoint); - } - return offset - deleteCharCount; - } - - /** - * Following if statements for Emoji tag sequence and Variation selector are skipping these - * modifiers for going through the last statement that is for handling emojis. They return the - * offset if they don't find proper base characters - */ - // Emoji Tag Sequence - if (codePoint == CANCEL_TAG) { // tag_end - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - while (lastOffset > 0 && FlutterTextUtils.isTagSpecChar(codePoint)) { // tag_spec - deleteCharCount += FlutterTextUtils.charCount(codePoint); - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - } - if (!FlutterTextUtils.isEmoji(codePoint)) { // tag_base not found. Just delete the end. - return offset - 2; - } - deleteCharCount += FlutterTextUtils.charCount(codePoint); - } - - if (FlutterTextUtils.isVariationSelector(codePoint)) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - if (!FlutterTextUtils.isEmoji(codePoint)) { - return offset - deleteCharCount; - } - deleteCharCount += FlutterTextUtils.charCount(codePoint); - - lastOffset -= FlutterTextUtils.charCount(codePoint); - } - - if (FlutterTextUtils.isEmoji(codePoint)) { - let isZwj: boolean = false; - let lastSeenVariantSelectorCharCount: number = 0; - do { - if (isZwj) { - deleteCharCount += FlutterTextUtils.charCount(codePoint) + lastSeenVariantSelectorCharCount + 1; - isZwj = false; - } - lastSeenVariantSelectorCharCount = 0; - if (FlutterTextUtils.isEmojiModifier(codePoint)) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - if (lastOffset > 0 && FlutterTextUtils.isVariationSelector(codePoint)) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - if (!FlutterTextUtils.isEmoji(codePoint)) { - return offset - deleteCharCount; - } - lastSeenVariantSelectorCharCount = FlutterTextUtils.charCount(codePoint); - lastOffset -= FlutterTextUtils.charCount(codePoint); - } - if (FlutterTextUtils.isEmojiModifierBase(codePoint)) { - deleteCharCount += lastSeenVariantSelectorCharCount + FlutterTextUtils.charCount(codePoint); - } - break; - } - - if (lastOffset > 0) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - if (codePoint == ZERO_WIDTH_JOINER) { - isZwj = true; - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastOffset -= FlutterTextUtils.charCount(codePoint); - if (lastOffset > 0 && FlutterTextUtils.isVariationSelector(codePoint)) { - codePoint = FlutterTextUtils.codePointBefore(text, lastOffset); - lastSeenVariantSelectorCharCount = FlutterTextUtils.charCount(codePoint); - lastOffset -= FlutterTextUtils.charCount(codePoint); - } - } - } - - if (lastOffset == 0) { - break; - } - } while (isZwj && FlutterTextUtils.isEmoji(codePoint)); - - if (isZwj && lastOffset == 0) { - deleteCharCount += FlutterTextUtils.charCount(codePoint) + lastSeenVariantSelectorCharCount + 1; - isZwj = false; - } - } - - return offset - deleteCharCount; - } - - /** - * Gets the offset after the current position, handling complex Unicode sequences - * such as emojis, regional indicators, keycaps, and variation selectors. - * @param text - The text to examine - * @param offset - The current offset position - * @returns The offset after the current position, accounting for Unicode sequences - */ - static getOffsetAfter(text: string, offset: number): number { - const len = text.length; - if (offset >= len - 1) { - return len; - } - - let codePoint: number = FlutterTextUtils.codePointAt(text, offset); - let nextCharCount: number = FlutterTextUtils.charCount(codePoint); - let nextOffset: number = offset + nextCharCount; - - if (nextOffset == 0) { - return 0; - } - // Line Feed - if (codePoint == LINE_FEED) { - codePoint = FlutterTextUtils.codePointAt(text, nextOffset); - if (codePoint == CARRIAGE_RETURN) { - ++nextCharCount; - } - return offset + nextCharCount; - } - - // Flags - if (FlutterTextUtils.isRegionalIndicatorSymbol(codePoint)) { - if (nextOffset >= len - 1 - || !FlutterTextUtils.isRegionalIndicatorSymbol(FlutterTextUtils.codePointAt(text, nextOffset))) { - return offset + nextCharCount; - } - // In this case there are at least two regional indicator symbols ahead of - // offset. If those two regional indicator symbols are a pair that - // represent a region together, the next offset should be after both of - // them. - let regionalIndicatorSymbolCount: number = 0; - let regionOffset: number = offset; - while (regionOffset > 0 - && FlutterTextUtils.isRegionalIndicatorSymbol(FlutterTextUtils.codePointBefore(text, regionOffset))) { - regionOffset -= FlutterTextUtils.charCount(FlutterTextUtils.codePointBefore(text, regionOffset)); - regionalIndicatorSymbolCount++; - } - if (regionalIndicatorSymbolCount % 2 == 0) { - nextCharCount += 2; - } - return offset + nextCharCount; - } - - // Keycaps - if (FlutterTextUtils.isKeycapBase(codePoint)) { - nextCharCount += FlutterTextUtils.charCount(codePoint); - } - if (codePoint == COMBINING_ENCLOSING_KEYCAP) { - codePoint = FlutterTextUtils.codePointBefore(text, nextOffset); - nextOffset += FlutterTextUtils.charCount(codePoint); - if (nextOffset < len && FlutterTextUtils.isVariationSelector(codePoint)) { - let tmpCodePoint: number = FlutterTextUtils.codePointAt(text, nextOffset); - if (FlutterTextUtils.isKeycapBase(tmpCodePoint)) { - nextCharCount += FlutterTextUtils.charCount(codePoint) + FlutterTextUtils.charCount(tmpCodePoint); - } - } else if (FlutterTextUtils.isKeycapBase(codePoint)) { - nextCharCount += FlutterTextUtils.charCount(codePoint); - } - return offset + nextCharCount; - } - - if (FlutterTextUtils.isEmoji(codePoint)) { - let isZwj: boolean = false; - let lastSeenVariantSelectorCharCount: number = 0; - do { - if (isZwj) { - nextCharCount += FlutterTextUtils.charCount(codePoint) + lastSeenVariantSelectorCharCount + 1; - isZwj = false; - } - lastSeenVariantSelectorCharCount = 0; - if (FlutterTextUtils.isEmojiModifier(codePoint)) { - break; - } - - if (nextOffset < len) { - codePoint = FlutterTextUtils.codePointAt(text, nextOffset); - nextOffset += FlutterTextUtils.charCount(codePoint); - if (codePoint == COMBINING_ENCLOSING_KEYCAP) { - codePoint = FlutterTextUtils.codePointBefore(text, nextOffset); - nextOffset += FlutterTextUtils.charCount(codePoint); - if (nextOffset < len && FlutterTextUtils.isVariationSelector(codePoint)) { - let tmpCodePoint: number = FlutterTextUtils.codePointAt(text, nextOffset); - if (FlutterTextUtils.isKeycapBase(tmpCodePoint)) { - nextCharCount += FlutterTextUtils.charCount(codePoint) + FlutterTextUtils.charCount(tmpCodePoint); - } - } else if (FlutterTextUtils.isKeycapBase(codePoint)) { - nextCharCount += FlutterTextUtils.charCount(codePoint); - } - return offset + nextCharCount; - } - if (FlutterTextUtils.isEmojiModifier(codePoint)) { - nextCharCount += lastSeenVariantSelectorCharCount + FlutterTextUtils.charCount(codePoint); - break; - } - if (FlutterTextUtils.isVariationSelector(codePoint)) { - nextCharCount += lastSeenVariantSelectorCharCount + FlutterTextUtils.charCount(codePoint); - break; - } - if (codePoint == ZERO_WIDTH_JOINER) { - isZwj = true; - codePoint = FlutterTextUtils.codePointAt(text, nextOffset); - nextOffset += FlutterTextUtils.charCount(codePoint); - if (nextOffset < len && FlutterTextUtils.isVariationSelector(codePoint)) { - codePoint = FlutterTextUtils.codePointAt(text, nextOffset); - lastSeenVariantSelectorCharCount = FlutterTextUtils.charCount(codePoint); - nextOffset += FlutterTextUtils.charCount(codePoint); - } - } - } - - if (nextOffset >= len) { - break; - } - } while (isZwj && FlutterTextUtils.isEmoji(codePoint)); - - if (isZwj && nextOffset >= len) { - nextCharCount += FlutterTextUtils.charCount(codePoint) + lastSeenVariantSelectorCharCount + 1; - isZwj = false; - } - } - - return offset + nextCharCount; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/localization/LocalizationPlugin.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/localization/LocalizationPlugin.ets deleted file mode 100644 index cf50893..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/localization/LocalizationPlugin.ets +++ /dev/null @@ -1,123 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on LocalizationPlugin.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import LocalizationChannel, { - LocalizationMessageHandler -} from '../../embedding/engine/systemchannels/LocalizationChannel' -import common from '@ohos.app.ability.common'; -import intl from '@ohos.intl'; -import Log from '../../util/Log'; -import i18n from '@ohos.i18n'; - -const TAG = "LocalizationPlugin"; - -/** - * Plugin for handling localization in Flutter applications. - * This class manages the interaction between Flutter's localization system - * and the OpenHarmony resource management system. - */ -export default class LocalizationPlugin { - private localizationChannel: LocalizationChannel; - private context: common.Context; - - /** - * Converts a locale string to an intl.Locale object. - * @param localeString - The locale string (e.g., "en_US" or "zh-CN") - * @returns The corresponding intl.Locale object - */ - localeFromString(localeString: string): intl.Locale { - localeString = localeString.replace('_', '-'); - let parts: string[] = localeString.split('-', -1); - let languageCode = parts[0]; - let scriptCode = ""; - let countryCode = ""; - let index: number = 1; - - if (parts.length > index && parts[index].length == 4) { - scriptCode = parts[index]; - index++; - } - - if (parts.length > index && parts[index].length >= 2 && parts[index].length <= 3) { - countryCode = parts[index]; - index++; - } - return new intl.Locale(languageCode + '-' + countryCode + '-' + scriptCode); - } - - private localizationMessageHandler: LocalizationMessageHandler = - new enterGetStringResource((key: string, localeString: string | null) => { - - Log.i(TAG, "getStringResource,key: " + key + ",localeString: " + localeString); - let localContext: common.Context = this.context; - let stringToReturn: string | null = null; - // 获取资源管理器 - let resMgr = localContext.resourceManager; - - try { - // 如果localeString不为空,则更新为指定地区的资源管理器 - if (localeString) { - let overrideConfig = resMgr.getOverrideConfiguration(); - overrideConfig.locale = localeString; - let overrideResMgr = resMgr.getOverrideResourceManager(overrideConfig); - stringToReturn = overrideResMgr.getStringByNameSync(key); - } else { - stringToReturn = resMgr.getStringByNameSync(key); - } - } catch (e) { - Log.e(TAG, e); - return null; - } - - return stringToReturn; - }) - - /** - * Constructs a new LocalizationPlugin instance. - * @param context - The application context - * @param localizationChannel - The LocalizationChannel for communication with Flutter - */ - constructor(context: common.Context, localizationChannel: LocalizationChannel) { - this.context = context; - this.localizationChannel = localizationChannel; - this.localizationChannel.setLocalizationMessageHandler(this.localizationMessageHandler); - } - - /** - * Sends the system locale to Flutter. - */ - sendLocaleToFlutter(): void { - let systemLocale: string = i18n.System.getSystemLocale(); - let data: Array = []; - data.push(systemLocale); - this.localizationChannel.sendLocales(data); - } -} - -/** - * Implementation of LocalizationMessageHandler for getting string resources. - */ -class enterGetStringResource { - /** - * The function to get string resources. - * @param key - The resource key - * @param localeString - The locale string, or null to use the default locale - * @returns The localized string, or null if not found - */ - getStringResource: (key: string, localeString: string | null) => string | null - - /** - * Constructs a new enterGetStringResource instance. - * @param getStringResource - The function to get string resources - */ - constructor(getStringResource: (key: string, localeString: string | null) => string | null) { - this.getStringResource = getStringResource - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/mouse/MouseCursorPlugin.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/mouse/MouseCursorPlugin.ets deleted file mode 100644 index 8330218..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/mouse/MouseCursorPlugin.ets +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on MouseCursorPlugin.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import MouseCursorChannel, { MouseCursorMethodHandler } from '../../embedding/engine/systemchannels/MouseCursorChannel'; -import pointer from '@ohos.multimodalInput.pointer'; -import HashMap from '@ohos.util.HashMap'; -import Log from '../../util/Log'; -import Any from '../common/Any'; - -const TAG: string = "MouseCursorPlugin"; - -/** - * Plugin for handling mouse cursor changes in Flutter applications. - * This class manages the interaction between Flutter's cursor system - * and the OpenHarmony pointer style system. - */ -export default class MouseCursorPlugin implements MouseCursorMethodHandler { - private mouseCursorChannel: MouseCursorChannel; - private systemCursorConstants: HashMap | null = null; - private windowId: number; - - /** - * Constructs a new MouseCursorPlugin instance. - * @param windowId - The window ID for setting pointer styles - * @param mouseCursorChannel - The MouseCursorChannel for communication with Flutter - */ - constructor(windowId: number, mouseCursorChannel: MouseCursorChannel) { - this.windowId = windowId; - this.mouseCursorChannel = mouseCursorChannel; - this.mouseCursorChannel.setMethodHandler(this); - } - - /** - * Activates a system cursor for the specified kind. - * @param kind - The cursor kind (e.g., "click", "text", "move") - */ - activateSystemCursor(kind: string): void { - if (this.windowId < 0) { - Log.w(TAG, "set point style failed windowId is invalid"); - return; - } - let pointStyle: pointer.PointerStyle = this.resolveSystemCursor(kind); - try { - pointer.setPointerStyle(this.windowId, pointStyle, (err: Any) => { - Log.i(TAG, "set point style success kind : " + kind); - }) - } catch (e) { - Log.e(TAG, "set point style failed : " + kind + " " + JSON.stringify(e)); - } - } - - private resolveSystemCursor(kind: string): pointer.PointerStyle { - if (this.systemCursorConstants == null) { - this.systemCursorConstants = new HashMap(); - this.systemCursorConstants.set("alias", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("allScroll", pointer.PointerStyle.MOVE); - this.systemCursorConstants.set("basic", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("cell", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("click", pointer.PointerStyle.HAND_POINTING); - this.systemCursorConstants.set("contextMenu", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("copy", pointer.PointerStyle.CURSOR_COPY); - this.systemCursorConstants.set("forbidden", pointer.PointerStyle.CURSOR_FORBID); - this.systemCursorConstants.set("grab", pointer.PointerStyle.HAND_OPEN); - this.systemCursorConstants.set("grabbing", pointer.PointerStyle.HAND_GRABBING); - this.systemCursorConstants.set("help", pointer.PointerStyle.HELP); - this.systemCursorConstants.set("move", pointer.PointerStyle.MOVE); - this.systemCursorConstants.set("none", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("noDrop", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("precise", pointer.PointerStyle.CROSS); - this.systemCursorConstants.set("text", pointer.PointerStyle.TEXT_CURSOR); - this.systemCursorConstants.set("resizeColum", pointer.PointerStyle.NORTH_SOUTH); - this.systemCursorConstants.set("resizeDown", pointer.PointerStyle.SOUTH); - this.systemCursorConstants.set("resizeDownLeft", pointer.PointerStyle.SOUTH_WEST); - this.systemCursorConstants.set("resizeDownRight", pointer.PointerStyle.SOUTH_EAST); - this.systemCursorConstants.set("resizeLeft", pointer.PointerStyle.WEST); - this.systemCursorConstants.set("resizeLeftRight", pointer.PointerStyle.RESIZE_LEFT_RIGHT); - this.systemCursorConstants.set("resizeRight", pointer.PointerStyle.EAST); - this.systemCursorConstants.set("resizeRow", pointer.PointerStyle.WEST_EAST); - this.systemCursorConstants.set("resizeUp", pointer.PointerStyle.NORTH); - this.systemCursorConstants.set("resizeUpDown", pointer.PointerStyle.RESIZE_UP_DOWN); - this.systemCursorConstants.set("resizeUpLeft", pointer.PointerStyle.NORTH_WEST); - this.systemCursorConstants.set("resizeUpRight", pointer.PointerStyle.NORTH_EAST); - this.systemCursorConstants.set("resizeUpLeftDownRight", pointer.PointerStyle.NORTH_WEST_SOUTH_EAST); - this.systemCursorConstants.set("resizeUpRightDownLeft", pointer.PointerStyle.NORTH_EAST_SOUTH_WEST); - this.systemCursorConstants.set("verticalText", pointer.PointerStyle.TEXT_CURSOR); - this.systemCursorConstants.set("wait", pointer.PointerStyle.DEFAULT); - this.systemCursorConstants.set("zoomIn", pointer.PointerStyle.ZOOM_IN); - this.systemCursorConstants.set("zoomOut", pointer.PointerStyle.ZOOM_OUT); - this.systemCursorConstants.set("middleBtnEast", pointer.PointerStyle.MIDDLE_BTN_EAST); - this.systemCursorConstants.set("middleBtnWest", pointer.PointerStyle.MIDDLE_BTN_WEST); - this.systemCursorConstants.set("middleBtnSouth", pointer.PointerStyle.MIDDLE_BTN_SOUTH); - this.systemCursorConstants.set("middleBtnNorth", pointer.PointerStyle.MIDDLE_BTN_NORTH); - this.systemCursorConstants.set("middleBtnNorthSouth", pointer.PointerStyle.MIDDLE_BTN_NORTH_SOUTH); - this.systemCursorConstants.set("middleBtnNorthEast", pointer.PointerStyle.MIDDLE_BTN_NORTH_EAST); - this.systemCursorConstants.set("middleBtnNorthWest", pointer.PointerStyle.MIDDLE_BTN_NORTH_WEST); - this.systemCursorConstants.set("middleBtnSouthEast", pointer.PointerStyle.MIDDLE_BTN_SOUTH_EAST); - this.systemCursorConstants.set("middleBtnSouthWest", pointer.PointerStyle.MIDDLE_BTN_SOUTH_WEST); - this.systemCursorConstants.set("middleBtnNorthSouthWestEast", - pointer.PointerStyle.MIDDLE_BTN_NORTH_SOUTH_WEST_EAST); - this.systemCursorConstants.set("horizontalTextCursor", pointer.PointerStyle.HORIZONTAL_TEXT_CURSOR); - this.systemCursorConstants.set("cursorCross", pointer.PointerStyle.CURSOR_CROSS); - this.systemCursorConstants.set("cursorCircle", pointer.PointerStyle.CURSOR_CIRCLE); - this.systemCursorConstants.set("loading", pointer.PointerStyle.LOADING); - this.systemCursorConstants.set("running", pointer.PointerStyle.RUNNING); - this.systemCursorConstants.set("colorSucker", pointer.PointerStyle.COLOR_SUCKER); - this.systemCursorConstants.set("screenshotChoose", pointer.PointerStyle.SCREENSHOT_CHOOSE); - this.systemCursorConstants.set("screenshotCursor", pointer.PointerStyle.SCREENSHOT_CURSOR); - } - let pointStyle: pointer.PointerStyle = this.systemCursorConstants.get(kind); - if (pointStyle === null) { - return pointer.PointerStyle.DEFAULT; - } - return pointStyle; - } - - /** - * Destroys the mouse cursor plugin and cleans up resources. - * The MouseCursorPlugin instance should not be used after calling this. - */ - destroy(): void { - this.mouseCursorChannel.setMethodHandler(null); - } -} - diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/CustomTouchEvent.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/CustomTouchEvent.ets deleted file mode 100644 index 6475db4..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/CustomTouchEvent.ets +++ /dev/null @@ -1,184 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -*/ - -/** - * Custom implementation of TouchEvent for platform views. - * This class wraps touch event data for communication between Flutter and native views. - */ -export class CustomTouchEvent implements TouchEvent { - /** The type of touch event. */ - type: TouchType = 0; - /** Array of all touch points in this event. */ - touches: CustomTouchObject[]; - /** Array of touch points that changed in this event. */ - changedTouches: CustomTouchObject[]; - /** Function to stop event propagation. */ - stopPropagation: () => void = () => { - }; - /** The timestamp when the event occurred. */ - timestamp: number; - /** The source type of the touch event. */ - source: SourceType; - /** The pressure value of the touch. */ - pressure: number; - /** The X-axis tilt value of the touch. */ - tiltX: number; - /** The Y-axis tilt value of the touch. */ - tiltY: number; - /** The source tool type for the touch. */ - sourceTool: SourceTool; - - /** - * Constructs a new CustomTouchEvent instance. - * @param type - The touch event type - * @param touches - Array of all touch points - * @param changedTouches - Array of touch points that changed - * @param timestamp - The event timestamp - * @param source - The source type of the touch - * @param pressure - The pressure value - * @param tiltX - The X-axis tilt value - * @param tiltY - The Y-axis tilt value - * @param sourceTool - The source tool type - */ - constructor(type: TouchType, touches: CustomTouchObject[], changedTouches: CustomTouchObject[], timestamp: number, - source: SourceType, pressure: number, tiltX: number, tiltY: number, sourceTool: SourceTool) { - this.type = type; - this.touches = touches; - this.changedTouches = changedTouches; - this.timestamp = timestamp; - this.source = source; - this.pressure = pressure; - this.tiltX = tiltX; - this.tiltY = tiltY; - this.sourceTool = sourceTool; - } - - /** Function to prevent the default action. */ - preventDefault: () => void = () => { - }; - - /** - * Gets the modifier key state. - * @param keys - Array of key names to check - * @returns True if any of the keys are pressed - * @throws Error as this method is not implemented - */ - getModifierKeyState(keys: string[]): boolean { - throw new Error('Method not implemented.'); - } - - /** The event target for this touch event. */ - target: EventTarget = new CustomEventTarget(new CustomArea(0, 0, { x: 0, y: 0 }, { x: 0, y: 0 })); - - /** - * Gets historical touch points. - * @returns Array of historical points - * @throws Error as this method is not implemented - */ - getHistoricalPoints(): HistoricalPoint[] { - throw new Error('Method not implemented.'); - } -} - -/** - * Custom implementation of EventTarget for touch events. - * This class provides a target for touch events in platform views. - */ -class CustomEventTarget implements EventTarget { - /** The area associated with this event target */ - area: Area = new CustomArea(0, 0, { x: 0, y: 0 }, { x: 0, y: 0 }); - - /** - * Constructs a new CustomEventTarget instance. - * @param area - The area associated with this event target - */ - constructor(area: Area) { - this.area = area; - } -} - -/** - * Custom implementation of Area for touch events. - * This class represents a rectangular area with position and size information. - */ -class CustomArea implements Area { - /** The width of the area. */ - width: Length = 0; - /** The height of the area. */ - height: Length = 0; - /** The local position of the area. */ - position: Position = { x: 0, y: 0 }; - /** The global position of the area. */ - globalPosition: Position = { x: 0, y: 0 }; - - /** - * Constructs a new CustomArea instance. - * @param width - The width of the area - * @param height - The height of the area - * @param position - The local position - * @param globalPosition - The global position - */ - constructor(width: Length, height: Length, position: Position, globalPosition: Position) { - this.width = width; - this.height = height; - this.position = position; - this.globalPosition = globalPosition; - } -} - -/** - * Custom implementation of TouchObject for platform views. - */ -export class CustomTouchObject implements TouchObject { - /** The type of touch. */ - type: TouchType; - /** The unique identifier for this touch point. */ - id: number; - /** The X coordinate in display space. */ - displayX: number; - /** The Y coordinate in display space. */ - displayY: number; - /** The X coordinate in window space. */ - windowX: number; - /** The Y coordinate in window space. */ - windowY: number; - /** The X coordinate in screen space. */ - screenX: number; - /** The Y coordinate in screen space. */ - screenY: number; - /** The X coordinate in local space. */ - x: number; - /** The Y coordinate in local space. */ - y: number; - - /** - * Constructs a new CustomTouchObject instance. - * @param type - The touch type - * @param id - The touch point ID - * @param displayX - The X coordinate in display space - * @param displayY - The Y coordinate in display space - * @param windowX - The X coordinate in window space - * @param windowY - The Y coordinate in window space - * @param screenX - The X coordinate in screen space - * @param screenY - The Y coordinate in screen space - * @param x - The X coordinate in local space - * @param y - The Y coordinate in local space - */ - constructor(type: TouchType, id: number, displayX: number, displayY: number, windowX: number, windowY: number, - screenX: number, screenY: number, x: number, y: number) { - this.type = type; - this.id = id; - this.displayX = displayX; - this.displayY = displayY; - this.windowX = windowX; - this.windowY = windowY; - this.screenX = screenX; - this.screenY = screenY; - this.x = x; - this.y = y; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView.ets deleted file mode 100644 index 0a80743..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformView.ets +++ /dev/null @@ -1,127 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformView.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import { DVModel, DynamicView } from '../../view/DynamicView/dynamicView' - -/** - * Parameters for platform view rendering. - * This class contains the configuration needed to render a platform view. - */ -export declare class Params { - /** The text direction for the platform view */ - direction: Direction - /** The platform view instance to render */ - platformView: PlatformView -} - -export declare class PlatformViewVisibleAreaEventOptions { - enable: boolean // Enable variable area monitoring - ratios: Array // Ratio thresholds for visible area changes, e.g., [0.0, 1.0] means 0% to 100% visible - expectedUpdateInterval: number // Expected interval for visible area updates in milliseconds - onInactiveThreshold: number // Texture continuous production pause operation visible area threshold - onActiveThreshold: number // Texture continuous production activation operation visible area threshold -} - -// unit: ms -const defaultExpectedUpdateInterval: number = 1000; - -/** A handle to an DynamicView to be embedded in the Flutter hierarchy. */ -export default abstract class PlatformView { - - /** - * Gets the type of this platform view. - * @returns The view type string - */ - getType(): string { - return 'default'; - } - - /** Returns the DynamicView to be embedded in the Flutter hierarchy. */ - abstract getView(): WrappedBuilder<[Params]>; - - /** - * Called by the FlutterEngine that owns this PlatformView when the DynamicView responsible - * for rendering a Flutter UI is associated with the FlutterEngine. - * - * This means that our associated FlutterEngine can now render a UI and interact with the user. - * - * Some platform views may have unusual dependencies on the DynamicView that renders Flutter - * UIs, such as unique keyboard interactions. That DynamicView is provided here for those - * purposes. Use of this DynamicView should be avoided if it is not absolutely necessary, because - * depending on this DynamicView will tend to make platform view code more brittle to future - * changes. - * @param dvModel - The DynamicView model associated with the Flutter view - */ - onFlutterViewAttached(dvModel: DVModel): void { - } - - /** - * Called by the FlutterEngine that owns this PlatformView when the DynamicView responsible - * for rendering a Flutter UI is detached and disassociated from the FlutterEngine. - * - * This means that our associated FlutterEngine no longer has a rendering surface, or a user - * interaction surface of any kind. - * - * This platform view must release any references related to the DynamicView that was - * provided in onFlutterViewAttached. - */ - onFlutterViewDetached(): void { - } - - /** - * Disposes this platform view. - * - * The PlatformView object is unusable after this method is called. - * - * Plugins implementing PlatformView must clear all references to the DynamicView object and - * the PlatformView after this method is called. Failing to do so will result in a memory leak. - * - * References related to the DynamicView attached in onFlutterViewAttached - * must be released in dispose() to avoid memory leaks. - */ - abstract dispose(): void; - - /** - * Callback fired when the platform's input connection is locked, or should be used. - * - * This hook only exists for rare cases where the plugin relies on the state of the input - * connection. This probably doesn't need to be implemented. - */ - onInputConnectionLocked(): void { - } - - /** - * Callback fired when the platform input connection has been unlocked. - * - * This hook only exists for rare cases where the plugin relies on the state of the input - * connection. This probably doesn't need to be implemented. - */ - onInputConnectionUnlocked(): void { - } - - // Obtain the parameters related to the changes in the visible area of the external texture - getPlatformViewVisibleAreaEventOptions(): PlatformViewVisibleAreaEventOptions { - return { - enable: false, - ratios: [0.0, 1.0], - expectedUpdateInterval: defaultExpectedUpdateInterval, - onInactiveThreshold : 0.0, - onActiveThreshold : 1.0 - } as PlatformViewVisibleAreaEventOptions; - } - - // The operation to pause the continuous production of external textures, including animations and videos - onInactive(): void { - } - - // The operation to resume the continuous production of external textures, including animations and videos - onActive(): void { - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewFactory.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewFactory.ets deleted file mode 100644 index 74562f9..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewFactory.ets +++ /dev/null @@ -1,51 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformViewFactory.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import MessageCodec from '../common/MessageCodec'; -import PlatformView from './PlatformView' -import common from '@ohos.app.ability.common'; -import Any from '../common/Any'; - -/** - * Factory for creating platform views. - * Subclasses must implement the create method to instantiate platform views. - */ -export default abstract class PlatformViewFactory { - private createArgsCodec: MessageCodec; - - /** - * Constructs a new PlatformViewFactory instance. - * @param createArgsCodec - The codec used to decode the args parameter of create - */ - constructor(createArgsCodec: MessageCodec) { - this.createArgsCodec = createArgsCodec; - } - - /** - * Creates a new platform view to be embedded in the Flutter hierarchy. - * - * @param context - The context to be used when creating the view, this is different than - * FlutterView's context - * @param viewId - Unique identifier for the created instance, this value is known on the Dart side - * @param args - Arguments sent from the Flutter app. The bytes for this value are decoded using the - * createArgsCodec argument passed to the constructor. This is null if createArgsCodec was - * null, or no arguments were sent from the Flutter app - * @returns A new PlatformView instance - */ - public abstract create(context: common.Context, viewId: number, args: Any): PlatformView; - - /** - * Returns the codec to be used for decoding the args parameter of create. - * @returns The MessageCodec instance - */ - getCreateArgsCodec(): MessageCodec { - return this.createArgsCodec; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewRegistry.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewRegistry.ets deleted file mode 100644 index 53860e8..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewRegistry.ets +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformViewRegistry.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import PlatformViewFactory from './PlatformViewFactory' - -/** - * Registry for platform view factories. - * - * Plugins can register factories for specific view types. - */ -export default interface PlatformViewRegistry { - /** - * Registers a factory for a platform view. - * - * @param viewTypeId - Unique identifier for the platform view's type - * @param factory - Factory for creating platform views of the specified type - * @returns True if succeeded, false if a factory is already registered for viewTypeId - */ - registerViewFactory(viewTypeId: string, factory: PlatformViewFactory): boolean; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewRegistryImpl.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewRegistryImpl.ets deleted file mode 100644 index 39f34f4..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewRegistryImpl.ets +++ /dev/null @@ -1,52 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformViewRegistryImpl.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import HashMap from '@ohos.util.HashMap'; -import PlatformViewFactory from './PlatformViewFactory' -import PlatformViewRegistry from './PlatformViewRegistry' - -/** - * Implementation of PlatformViewRegistry for managing platform view factories. - */ -export default class PlatformViewRegistryImpl implements PlatformViewRegistry { - /** Maps a platform view type id to its factory. */ - private viewFactories: HashMap; - - /** - * Constructs a new PlatformViewRegistryImpl instance. - */ - constructor() { - this.viewFactories = new HashMap(); - } - - /** - * Registers a factory for a platform view. - * @param viewTypeId - Unique identifier for the platform view's type - * @param factory - Factory for creating platform views of the specified type - * @returns True if succeeded, false if a factory is already registered for viewTypeId - */ - registerViewFactory(viewTypeId: string, factory: PlatformViewFactory): boolean { - if (this.viewFactories.hasKey(viewTypeId)) { - return false; - } - - this.viewFactories.set(viewTypeId, factory); - return true; - } - - /** - * Gets the factory for a specific view type. - * @param viewTypeId - The view type ID - * @returns The PlatformViewFactory for the view type, or undefined if not found - */ - getFactory(viewTypeId: string): PlatformViewFactory { - return this.viewFactories.get(viewTypeId); - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewWrapper.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewWrapper.ets deleted file mode 100644 index ac3858a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewWrapper.ets +++ /dev/null @@ -1,131 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import OhosTouchProcessor from '../../embedding/ohos/OhosTouchProcessor'; -import { DVModel, DVModelParameters } from '../../view/DynamicView/dynamicView'; -import { createDVModelFromJson } from '../../view/DynamicView/dynamicViewJson'; -import { RootDvModeManager } from './RootDvModelManager'; -import matrix4 from '@ohos.matrix4' -import Log from '../../util/Log'; -import Any from '../common/Any'; - -const TAG: string = "PlatformViewWrapper"; - -/** - * Wraps a platform view to intercept gestures and project this view onto a rendering target. - * - * An OpenHarmony platform view is composed by the engine using a TextureLayer. The view is embedded - * in the OpenHarmony view hierarchy like a normal DynamicView, but it's projected onto a rendering - * target, so it can be efficiently composed by the engine. - * - * Since the view is in the OpenHarmony view hierarchy, keyboard and accessibility interactions - * behave normally. - */ -export class PlatformViewWrapper { - private prevLeft: number = 0; - private prevTop: number = 0; - private left: number = 0; - private top: number = 0; - private bufferWidth: number = 0; - private bufferHeight: number = 0; - private touchProcessor: OhosTouchProcessor | null = null; - private model: DVModel | undefined; - - /** - * Sets the touch processor for handling touch events. - * @param newTouchProcessor - The OhosTouchProcessor instance - */ - public setTouchProcessor(newTouchProcessor: OhosTouchProcessor): void { - this.touchProcessor = newTouchProcessor; - } - - /** - * Constructs a new PlatformViewWrapper instance. - */ - constructor() { - } - - /** - * Gets the DynamicView model associated with this wrapper. - * @returns The DVModel instance - */ - public getDvModel(): DVModel { - return this.model!; - } - - /** - * Sets a parameter value in the DVModelParameters. - * @param params - The parameters object to modify - * @param key - The parameter key - * @param element - The value to set - */ - setParams: (params: DVModelParameters, key: string, element: Any) => void = - (params: DVModelParameters, key: string, element: Any): void => { - let params2 = params as Record; - params2[key] = element; - } - - /** - * Gets a parameter value from the DVModelParameters. - * @param params - The parameters object to read from - * @param element - The parameter key - * @returns The parameter value, or undefined if not found - */ - getParams: (params: DVModelParameters, element: string) => string | Any = - (params: DVModelParameters, element: string): string | Any => { - let params2 = params as Record; - return params2[element]; - } - - /** - * Sets the layout parameters for the platform view. - * @param parameters - The layout parameters to apply - */ - public setLayoutParams(parameters: DVModelParameters): void { - if (!this.model) { - return; - } - if (this.model.params == null) { - this.model.params = new DVModelParameters(); - } - this.setParams(this.model.params, "marginLeft", this.getParams(parameters, "marginLeft")); - this.setParams(this.model.params, "marginTop", this.getParams(parameters, "marginTop")); - this.left = this.getParams(parameters, "marginLeft"); - this.top = this.getParams(parameters, "marginTop"); - - this.setParams(this.model.params, "width", this.getParams(parameters, "width")); - this.setParams(this.model.params, "height", this.getParams(parameters, "height")); - } - - /** - * Adds a DynamicView model to this wrapper. - * @param model - The DVModel to add - */ - public addDvModel(model: DVModel): void { - this.model = model - } -} - -/** - * Parameters for DynamicView model creation. - * This class holds the basic structure for creating a DynamicView model. - */ -class DVModelParam { - /** The component type */ - compType: string - /** Array of child components */ - children: [] - - /** - * Constructs a new DVModelParam instance. - * @param compType - The component type - * @param children - Array of child components - */ - constructor(compType: string, children: []) { - this.compType = compType; - this.children = children; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewsController.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewsController.ets deleted file mode 100644 index 8c2e578..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/PlatformViewsController.ets +++ /dev/null @@ -1,767 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on PlatformViewsController.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import PlatformViewsChannel, { - PlatformViewBufferResized, - PlatformViewCreationRequest, - PlatformViewResizeRequest, - PlatformViewsHandler, - PlatformViewTouch, - PlatformViewBufferSize -} from '../../../ets/embedding/engine/systemchannels/PlatformViewsChannel'; -import PlatformView, { Params } from './PlatformView'; -import { DVModelParameters, } from '../../view/DynamicView/dynamicView'; -import { createDVModelFromJson } from '../../view/DynamicView/dynamicViewJson'; -import display from '@ohos.display'; -import { FlutterView } from '../../view/FlutterView'; -import { TextureRegistry } from '../../view/TextureRegistry'; -import TextInputPlugin from '../editing/TextInputPlugin'; -import { PlatformViewWrapper } from './PlatformViewWrapper'; -import { FlutterOverlaySurface } from '../../embedding/engine/FlutterOverlaySurface'; -import HashSet from '@ohos.util.HashSet'; -import PlatformViewRegistry from './PlatformViewRegistry'; -import PlatformViewRegistryImpl from './PlatformViewRegistryImpl'; -import DartExecutor from '../../embedding/engine/dart/DartExecutor'; -import { FlutterMutatorView } from '../../embedding/engine/mutatorsstack/FlutterMutatorView'; -import Log from '../../util/Log' -import PlatformViewFactory from './PlatformViewFactory' -import { ByteBuffer } from '../../util/ByteBuffer'; -import Any from '../common/Any'; -import { ArrayList, Stack } from '@kit.ArkTS'; -import { CustomTouchEvent, CustomTouchObject } from './CustomTouchEvent'; -import { NodeRenderType } from '@kit.ArkUI'; -import { PlatformViewInfo } from '../../embedding/ohos/PlatformViewInfo'; -import { EmbeddingNodeController } from '../../embedding/ohos/EmbeddingNodeController'; - -/** - * JSON representation of a DynamicView model. - */ -class DVModelJson { - /** The component type. */ - compType: string - /** Array of child components. */ - children: Array - /** Component attributes. */ - attributes: Any - /** Component events. */ - events: Any - /** Optional build function. */ - build: Any - - /** - * Constructs a new DVModelJson instance. - * @param compType - The component type - * @param children - Array of child components - * @param attributes - Component attributes - * @param events - Component events - * @param build - Optional build function - */ - constructor(compType: string, children: Array, attributes: Any, events: Any, build?: Any) { - this.compType = compType - this.children = children - this.attributes = attributes - this.events = events; - this.build = build; - } -} -/** - * Enumeration of touch event types. - */ -enum TouchEventType { - /** Action code for when a primary pointer touched the screen. */ - ACTION_DOWN = 0, - /** Action code for when a primary pointer stopped touching the screen. */ - ACTION_UP = 1, - /** Action code for when the event only includes information about pointer movement. */ - ACTION_MOVE = 2, - /** Action code for when a motion event has been canceled. */ - ACTION_CANCEL = 3, - /** Action code for when a secondary pointer touched the screen. */ - ACTION_POINTER_DOWN = 5, - /** Action code for when a secondary pointer stopped touching the screen. */ - ACTION_POINTER_UP = 6, -} - -const TAG = "PlatformViewsController" - -/** - * Controller for managing platform views in Flutter applications. - * This class handles the creation, lifecycle, and interaction of native views embedded in Flutter. - */ -export default class PlatformViewsController implements PlatformViewsHandler { - private registry: PlatformViewRegistryImpl; - private context: Context | null = null; - private flutterView: FlutterView | null = null; - private textureRegistry: TextureRegistry | null = null; - private textInputPlugin: TextInputPlugin | null = null; - private platformViewsChannel: PlatformViewsChannel | null = null; - private nextOverlayLayerId: number = 0; - private focusViewId: number = -1; - private platformViews: Map; - private viewIdWithTextureId: Map; - private viewIdWithNodeController: Map; - private viewWrappers: Map; - private currentFrameUsedOverlayLayerIds: HashSet; - private currentFrameUsedPlatformViewIds: HashSet; - - /** - * Constructs a new PlatformViewsController instance. - */ - constructor() { - this.registry = new PlatformViewRegistryImpl(); - this.currentFrameUsedOverlayLayerIds = new HashSet(); - this.currentFrameUsedPlatformViewIds = new HashSet(); - this.viewWrappers = new Map(); - this.platformViews = new Map(); - this.viewIdWithTextureId = new Map(); - this.viewIdWithNodeController = new Map(); - } - - /** - * Creates a platform view for hybrid composition mode. - * @param request - The platform view creation request - */ - createForPlatformViewLayer(request: PlatformViewCreationRequest): void { - Log.i(TAG, "Enter createForPlatformViewLayer"); - this.ensureValidRequest(request); - - let platformView: PlatformView = this.createPlatformView(request); - - this.configureForHybridComposition(platformView, request); - } - - /** - * Disposes a platform view and releases all associated resources. - * @param viewId - The ID of the platform view to dispose - */ - dispose(viewId: number): void { - let platformView: PlatformView | null = this.platformViews.get(viewId) || null; - if (platformView == null) { - Log.e(TAG, "Disposing unknown platform view with id: " + viewId); - return; - } - if (this.focusViewId == viewId) { - this.clearFocus(viewId); - this.focusViewId = -1; - } - this.platformViews.delete(viewId); - let textureId = this.viewIdWithTextureId.get(viewId); - - if (textureId != undefined) { - this.textureRegistry!.unregisterTexture(textureId); - } - - this.viewIdWithNodeController.get(viewId)?.disposeFrameNode() - this.viewIdWithNodeController.delete(viewId); - - let viewWrapper: PlatformViewWrapper | null = this.viewWrappers.get(viewId) || null; - if (viewWrapper != null && this.flutterView) { - let index = this.flutterView.getDVModel().children.indexOf(viewWrapper.getDvModel()!); - if (index > -1) { - this.flutterView.getDVModel().children.splice(index, 1); - platformView.onFlutterViewDetached(); - } - } - this.viewWrappers.delete(viewId); - - try { - platformView.dispose(); - } catch (err) { - Log.e(TAG, "Disposing platform view threw an exception", err); - } - } - - /** - * Sets a parameter value in the DVModelParameters. - * @param params - The parameters object to modify - * @param key - The parameter key - * @param element - The value to set - */ - setParams: (params: DVModelParameters, key: string, element: Any) => void = - (params: DVModelParameters, key: string, element: Any): void => { - let params2 = params as Record; - params2[key] = element; - } - - /** - * Gets a parameter value from the DVModelParameters. - * @param params - The parameters object to read from - * @param key - The parameter key - * @returns The parameter value as a number - */ - getParams: (params: DVModelParameters, key: string) => number = (params: DVModelParameters, key: string): number => { - let params2 = params as Record; - return params2[key]; - } - - /** - * Resizes a platform view. - * @param request - The resize request containing new dimensions - * @param onComplete - Callback to invoke when resize is complete - */ - resize(request: PlatformViewResizeRequest, onComplete: PlatformViewBufferResized): void { - let physicalWidth: number = this.toPhysicalPixels(request.newLogicalWidth); - let physicalHeight: number = this.toPhysicalPixels(request.newLogicalHeight); - let viewId: number = request.viewId; - Log.i(TAG, - `Resize viewId ${viewId}, pw:${physicalWidth}, ph:${physicalHeight},lw:${request.newLogicalWidth}, lh:${request.newLogicalHeight}`); - - let viewWrapper = this.viewWrappers.get(request.viewId) - let params: DVModelParameters | undefined = viewWrapper?.getDvModel()!.params - - this.setParams(params!, "width", physicalWidth); - this.setParams(params!, "height", physicalHeight); - - let textureId = this.viewIdWithTextureId.get(viewId); - if (textureId != undefined) { - let density = this.getDisplayDensity(); - this.textureRegistry?.notifyTextureResizing(textureId, request.newLogicalWidth * density, request.newLogicalHeight * density); - } - - onComplete.run(new PlatformViewBufferSize(physicalWidth, physicalHeight)); - } - - /** - * Updates the offset position of a platform view. - * @param viewId - The ID of the platform view - * @param top - The top offset - * @param left - The left offset - */ - offset(viewId: number, top: number, left: number): void { - Log.i(TAG, `Offset is id${viewId}, t:${top}, l:${left}`); - - let viewWrapper = this.viewWrappers.get(viewId) - if (viewWrapper === undefined) { - return; - } - - let params: DVModelParameters | undefined = viewWrapper?.getDvModel()!.params; - if (!params) { - return; - } - // When the current value is NaN and the previous value is a normal value, - // the platformView is considered to have transitioned from visible to invisible. - // Use Number.isNaN; the page is considered invisible in the background - // only when both top and left values equal NaN. - if (Number.isNaN(top) && Number.isNaN(left)) { - let leftPre: number | undefined = this.getParams(params!, "left"); - let topPre: number | undefined = this.getParams(params!, "top"); - let compType: string | undefined = viewWrapper?.getDvModel().compType; - if ((leftPre !== undefined && !Number.isNaN(leftPre)) && - (topPre !== undefined && !Number.isNaN(topPre)) && - (compType === "NodeContainer")) { - const nodeController = (params as Record)?.nodeController; - if (nodeController instanceof EmbeddingNodeController) { - nodeController.notifyPlatformViewInvisible(); - } - } - } - this.setParams(params!, "left", left); - this.setParams(params!, "top", top); - } - - /** - * Sets the hover state for platform views. - * @param viewId - The ID of the platform view to set hover state for - */ - hover(viewId: number) { - for (let key of this.viewWrappers.keys()) { - let viewWrapper: undefined | PlatformViewWrapper = this.viewWrappers.get(key); - let dvModel = viewWrapper?.getDvModel(); - let params = dvModel?.getLayoutParams() as Record; - if (key == viewId) { - params["hover"] = true; - } else { - params["hover"] = false; - } - } - } - - /** - * Handles touch events for platform views. - * @param touch - The touch event information - */ - onTouch(touch: PlatformViewTouch): void { - let viewWrapper: undefined | PlatformViewWrapper = this.viewWrappers.get(touch.viewId) - this.focusViewId = touch.viewId; - if (viewWrapper != undefined) { - let dvModel = viewWrapper.getDvModel() - let params = dvModel.getLayoutParams() as Record; - // When receiving a DOWN action - if (touch.action === TouchEventType.ACTION_DOWN) { - // Set the current touch state to true - params['down'] = true - // When first receiving a touch DOWN event, dispatch all events stored in the list - let touchEventArray: Array | undefined = params['touchEvent'] as Array - if (touchEventArray !== undefined) { - let nodeController = params['nodeController'] as EmbeddingNodeController; - for (let it of touchEventArray) { - nodeController.postEvent(it) - } - // Clear the list after first dispatch - params['touchEvent'] = undefined - } - - // When first receiving a mouse PRESS event, dispatch all events stored in the list - let mouseEventArray: Array | undefined = params['mouseEvent'] as Array - if (mouseEventArray !== undefined) { - let nodeController = params['nodeController'] as EmbeddingNodeController; - for (let it of mouseEventArray) { - nodeController.postMouseEvent(it) - } - // Clear the list after first dispatch - params['mouseEvent'] = undefined - } - // When receiving an UP action - } else if (touch.action === TouchEventType.ACTION_UP || touch.action === TouchEventType.ACTION_CANCEL) { - // Set the touch state to false after finger is lifted. When multiple fingers are lifted suddenly, - // the final state returned is also ACTION_UP, so we use the UP state to indicate the user - // is no longer touching the platform view - params['down'] = false - } - } - } - - /** - * Sets the text direction for a platform view. - * @param viewId - The ID of the platform view - * @param direction - The text direction to set - */ - setDirection(viewId: number, direction: Direction): void { - let nodeController = this.viewIdWithNodeController.get(viewId) - if (nodeController != undefined) { - nodeController?.setRenderOption(this.flutterView!.getPlatformView()!, this.flutterView!.getSurfaceId(), - NodeRenderType.RENDER_TYPE_TEXTURE, direction) - nodeController?.rebuild() - } - } - - /** - * Validates if a direction value is valid. - * @param direction - The direction value to validate - * @returns True if the direction is valid, false otherwise - */ - validateDirection(direction: number): boolean { - return direction == Direction.Ltr || direction == Direction.Rtl || direction == Direction.Auto; - } - - /** - * Clears focus from a platform view. - * @param viewId - The ID of the platform view - */ - clearFocus(viewId: number): void { - const platformView = this.platformViews.get(viewId); - if (platformView == null) { - Log.e(TAG, "Setting direction to an unknown view with id: " + viewId); - return; - } - const embeddedView = platformView.getView(); - if (embeddedView == null) { - Log.e(TAG, "Setting direction to a null view with id: " + viewId); - return; - } - // Make the Xcomponent gain focus. - focusControl.requestFocus("unfocus-xcomponent-node"); - } - - /** - * Synchronizes the native view hierarchy. - * @param yes - Whether to synchronize - * @throws Error as this method is not implemented - */ - synchronizeToNativeViewHierarchy(yes: boolean): void { - throw new Error('Method not implemented.'); - } - - /** - * Creates a platform view for texture layer composition mode. - * @param request - The platform view creation request - * @returns The texture ID for the created view - */ - public createForTextureLayer(request: PlatformViewCreationRequest): number { - Log.i(TAG, "Enter createForTextureLayer"); - this.ensureValidRequest(request); - - let platformView: PlatformView = this.createPlatformView(request); - let textureId = this.configureForTextureLayerComposition(platformView, request); - this.viewIdWithTextureId.set(request.viewId, textureId); - return textureId; - } - - /** - * Ensures that a platform view creation request is valid. - * @param request - The request to validate - * @throws Error if the request is invalid - * @private - */ - private ensureValidRequest(request: PlatformViewCreationRequest): void { - if (!this.validateDirection(request.direction)) { - throw new Error("Trying to create a view with unknown direction value: " - + request.direction - + "(view id: " - + request.viewId - + ")") - } - } - - /** - * Creates a platform view instance from a factory. - * @param request - The platform view creation request - * @returns The created PlatformView instance - * @throws Error if the factory is not found or creation fails - * @private - */ - private createPlatformView(request: PlatformViewCreationRequest): PlatformView { - Log.i(TAG, "begin createPlatformView"); - const viewFactory: PlatformViewFactory = this.registry.getFactory(request.viewType); - if (viewFactory == null) { - throw new Error("Trying to create a platform view of unregistered type: " + request.viewType) - } - - let createParams: Any = null; - if (request.params != null) { - let byteParas: ByteBuffer = request.params as ByteBuffer; - createParams = viewFactory.getCreateArgsCodec().decodeMessage(byteParas.buffer); - } - - if (this.context == null) { - throw new Error('PlatformView#context is null.'); - } - let platformView = viewFactory.create(this.context, request.viewId, createParams); - - let embeddedView: WrappedBuilder<[Params]> = platformView.getView(); - if (embeddedView == null) { - throw new Error("PlatformView#getView() returned null, but an WrappedBuilder reference was expected."); - } - - this.platformViews.set(request.viewId, platformView); - return platformView; - } - - /** - * Configures the view for Hybrid Composition mode. - * @param platformView - The platform view to configure - * @param request - The creation request - * @private - */ - private configureForHybridComposition(platformView: PlatformView, request: PlatformViewCreationRequest): void { - Log.i(TAG, "Using hybrid composition for platform view: " + request.viewId); - } - - /** - * Configures the view for Texture Layer Composition mode. - * @param platformView - The platform view to configure - * @param request - The creation request - * @returns The texture ID for the view - * @private - */ - private configureForTextureLayerComposition(platformView: PlatformView, - request: PlatformViewCreationRequest): number { - Log.i(TAG, "Hosting view in view hierarchy for platform view: " + request.viewId); - let surfaceId: string = '0'; - let textureId: number = 0; - if (this.textureRegistry != null) { - textureId = this.textureRegistry!.getTextureId(); - surfaceId = this.textureRegistry!.registerTexture(textureId).getSurfaceId().toString(); - Log.i(TAG, "nodeController getSurfaceId: " + surfaceId); - this.flutterView!.setSurfaceId(surfaceId); - } - - let wrappedBuilder: WrappedBuilder<[Params]> = platformView.getView(); - this.flutterView?.setWrappedBuilder(wrappedBuilder); - this.flutterView?.setPlatformView(platformView); - let physicalWidth: number = this.toPhysicalPixels(request.logicalWidth); - let physicalHeight: number = this.toPhysicalPixels(request.logicalHeight); - - let nodeController = new EmbeddingNodeController(); - nodeController.setRenderOption(platformView, surfaceId, NodeRenderType.RENDER_TYPE_TEXTURE, request.direction); - this.viewIdWithNodeController.set(request.viewId, nodeController); - - let dvModel = createDVModelFromJson(new DVModelJson("NodeContainer", - [], - { - "width": physicalWidth, - "height": physicalHeight, - "nodeController": nodeController, - "left": request.logicalLeft, - "top": request.logicalTop - }, - {}, - undefined)); - let viewWrapper: PlatformViewWrapper = new PlatformViewWrapper(); - viewWrapper.addDvModel(dvModel); - this.viewWrappers.set(request.viewId, viewWrapper); - this.flutterView?.getDVModel().children.push(viewWrapper.getDvModel()) - platformView.onFlutterViewAttached(this.flutterView!.getDVModel()); - Log.i(TAG, "Create platform view success"); - return textureId; - } - - /** - * Attaches the controller to a context, texture registry, and Dart executor. - * @param context - The application context - * @param textureRegistry - The texture registry for managing textures - * @param dartExecutor - The Dart executor for communication - */ - public attach(context: Context, textureRegistry: TextureRegistry | null, dartExecutor: DartExecutor): void { - this.context = context; - this.textureRegistry = textureRegistry; - this.platformViewsChannel = new PlatformViewsChannel(dartExecutor); - this.platformViewsChannel.setPlatformViewsHandler(this); - } - - /** - * Detaches the controller and cleans up resources. - */ - public detach(): void { - if (this.platformViewsChannel != null) { - this.platformViewsChannel.setPlatformViewsHandler(null); - } - this.destroyOverlaySurfaces(); - this.platformViewsChannel = null; - this.context = null; - this.textureRegistry = null; - } - - /** - * Attaches the controller to a FlutterView. - * @param newFlutterView - The FlutterView to attach to - */ - public attachToView(newFlutterView: FlutterView) { - this.flutterView = newFlutterView; - } - - /** - * Detaches the controller from the FlutterView. - */ - public detachFromView(): void { - this.destroyOverlaySurfaces(); - this.removeOverlaySurfaces(); - this.flutterView = null; - } - - /** - * Gets the current FlutterView. - * @returns The FlutterView instance, or null if not attached - */ - public getFlutterView(): FlutterView | null { - return this.flutterView; - } - - /** - * Attaches a text input plugin to this controller. - * @param textInputPlugin - The TextInputPlugin instance - */ - public attachTextInputPlugin(textInputPlugin: TextInputPlugin): void { - this.textInputPlugin = textInputPlugin; - } - - /** - * Detaches the text input plugin from this controller. - */ - public detachTextInputPlugin(): void { - this.textInputPlugin = null; - } - - /** - * Gets the platform view registry. - * @returns The PlatformViewRegistry instance - */ - public getRegistry(): PlatformViewRegistry { - return this.registry; - } - - /** - * Called when the controller is detached from NAPI. - * Disposes all platform views. - */ - public onDetachedFromNapi(): void { - this.diposeAllViews(); - } - - /** - * Called before the Flutter engine restarts. - * Disposes all platform views. - */ - public onPreEngineRestart(): void { - this.diposeAllViews(); - } - - /** - * Gets the display density. - * @returns The display density value - * @private - */ - private getDisplayDensity(): number { - return display.getDefaultDisplaySync().densityPixels; - } - - /** - * Converts logical pixels to physical pixels. - * @param logicalPixels - The logical pixel value - * @returns The physical pixel value - * @private - */ - private toPhysicalPixels(logicalPixels: number): number { - return Math.round(px2vp(logicalPixels * this.getDisplayDensity())); - } - - /** - * Converts physical pixels to logical pixels using a specific density. - * @param physicalPixels - The physical pixel value - * @param displayDensity - The display density to use - * @returns The logical pixel value - * @private - */ - private toLogicalPixelsByDensity(physicalPixels: number, displayDensity: number): number { - return Math.round(physicalPixels / displayDensity); - } - - /** - * Converts physical pixels to logical pixels using the current display density. - * @param physicalPixels - The physical pixel value - * @returns The logical pixel value - * @private - */ - private toLogicalPixels(physicalPixels: number): number { - return this.toLogicalPixelsByDensity(physicalPixels, this.getDisplayDensity()); - } - - /** - * Disposes all platform views. - * @private - */ - private diposeAllViews(): void { - let viewKeys = this.platformViews.keys(); - for (let viewId of viewKeys) { - this.dispose(viewId); - } - } - - /** - * Initializes the root image view if needed. - * @private - */ - private initializeRootImageViewIfNeeded(): void { - } - - /** - * Called when an overlay surface should be displayed. - * @param id - The overlay surface ID - * @param x - The X position - * @param y - The Y position - * @param width - The width - * @param height - The height - */ - public onDisplayOverlaySurface(id: number, x: number, y: number, width: number, height: number): void { - } - - /** - * Called at the beginning of each frame. - * Clears the sets of used overlay layers and platform views. - */ - public onBeginFrame(): void { - this.currentFrameUsedOverlayLayerIds.clear(); - this.currentFrameUsedPlatformViewIds.clear(); - } - - /** - * Called at the end of each frame. - */ - public onEndFrame(): void { - } - - /** - * Finishes frame rendering. - * @param isFrameRenderedUsingImageReaders - Whether the frame was rendered using image readers - * @private - */ - private finishFrame(isFrameRenderedUsingImageReaders: boolean): void { - } - - /** - * Creates a new overlay surface. - * @returns A new FlutterOverlaySurface instance - */ - public createOverlaySurface(): FlutterOverlaySurface { - return new FlutterOverlaySurface(this.nextOverlayLayerId++); - } - - /** - * Destroys all overlay surfaces. - * @private - */ - private destroyOverlaySurfaces(): void { - } - - /** - * Removes overlay surfaces from the view hierarchy. - * @private - */ - private removeOverlaySurfaces(): void { - if (!(this.flutterView instanceof FlutterView)) { - return; - } - } - - /** - * Renders a platform view with the specified dimensions and position. - * @param surfaceId - The surface ID - * @param platformView - The platform view to render - * @param width - The width in pixels - * @param height - The height in pixels - * @param left - The left position in pixels - * @param top - The top position in pixels - */ - public render(surfaceId: number, platformView: PlatformView, - width: number, height: number, left: number, top: number) { - - let wrapper = this.viewWrappers.get(surfaceId); - if (wrapper != null) { - let params: DVModelParameters | undefined = wrapper?.getDvModel()!.params - - this.setParams(params!, "width", width); - this.setParams(params!, "height", height); - this.setParams(params!, "left", left); - this.setParams(params!, "top", top); - return; - } - - this.flutterView!.setSurfaceId(surfaceId.toString()); - let wrappedBuilder: WrappedBuilder<[Params]> = platformView.getView(); - this.flutterView?.setWrappedBuilder(wrappedBuilder); - this.flutterView?.setPlatformView(platformView); - - let nodeController = new EmbeddingNodeController(); - - nodeController.setRenderOption(platformView, surfaceId.toString(), NodeRenderType.RENDER_TYPE_TEXTURE, - Direction.Auto); - this.viewIdWithNodeController.set(surfaceId, nodeController); - - let dvModel = createDVModelFromJson(new DVModelJson("NodeContainer", - [], - { - "width": width, - "height": height, - "nodeController": nodeController, - "left": left, - "top": top - }, - {}, - undefined)); - - let viewWrapper: PlatformViewWrapper = new PlatformViewWrapper(); - viewWrapper.addDvModel(dvModel); - this.viewWrappers.set(surfaceId, viewWrapper); - this.flutterView?.getDVModel().children.push(viewWrapper.getDvModel()); - platformView.onFlutterViewAttached(this.flutterView!.getDVModel()); - this.platformViews.set(surfaceId, platformView!); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/RawPointerCoord.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/RawPointerCoord.ets deleted file mode 100644 index bad2804..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/RawPointerCoord.ets +++ /dev/null @@ -1,63 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -*/ - -/** - * Represents raw pointer coordinates with additional touch information. - * This class holds detailed information about a pointer event. - */ -export class RawPointerCoords { - private orientation: number = 0; - private pressure: number = 0; - private size: number = 0; - private toolMajor: number = 0; - private toolMinor: number = 0; - private touchMajor: number = 0; - private touchMinor: number = 0; - private x: number = 0; - private y: number = 0; - - /** - * Constructs a new RawPointerCoords instance. - * @param orientation - The orientation angle in radians - * @param pressure - The pressure value (0.0 to 1.0) - * @param size - The size value - * @param toolMajor - The major axis of the tool ellipse - * @param toolMinor - The minor axis of the tool ellipse - * @param touchMajor - The major axis of the touch ellipse - * @param touchMinor - The minor axis of the touch ellipse - * @param x - The X coordinate - * @param y - The Y coordinate - */ - constructor(orientation: number, pressure: number, size: number, toolMajor: number, toolMinor: number, - touchMajor: number, touchMinor: number, x: number, y: number) { - this.orientation = orientation; - this.pressure = pressure; - this.size = size; - this.toolMajor = toolMajor; - this.toolMinor = toolMinor; - this.touchMajor = touchMajor; - this.touchMinor = touchMinor; - this.x = x; - this.y = y; - } - - /** - * Gets the X coordinate. - * @returns The X coordinate value - */ - getX(): number { - return this.x; - } - - /** - * Gets the Y coordinate. - * @returns The Y coordinate value - */ - getY(): number { - return this.y; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/RootDvModelManager.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/RootDvModelManager.ets deleted file mode 100644 index df11ae0..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/platform/RootDvModelManager.ets +++ /dev/null @@ -1,42 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import { - DVModel, - DVModelChildren, - DVModelContainer, - DVModelEvents, - DVModelParameters -} from '../../view/DynamicView/dynamicView'; -import Log from '../../util/Log'; - -/** - * Manager for the root DynamicView model container. - * This class provides a singleton root container for all platform views. - */ -export class RootDvModeManager { - private static model: DVModel = - new DVModel("Stack", new DVModelParameters(), new DVModelEvents(), new DVModelChildren(), null); - private static container: DVModelContainer = new DVModelContainer(RootDvModeManager.model); - - /** - * Gets the root DynamicView model container. - * @returns The root DVModelContainer instance - */ - public static getRootDvMode(): DVModelContainer { - return RootDvModeManager.container; - } - - /** - * Adds a DynamicView model to the root container. - * @param model - The DVModel to add - */ - public static addDvModel(model: DVModel): void { - RootDvModeManager.container.model.children.push(model); - Log.i("flutter RootDvModeManager", 'DVModel: %{public}s', - JSON.stringify(RootDvModeManager.container.model.children) ?? ''); - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/view/SensitiveContentPlugin.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/view/SensitiveContentPlugin.ets deleted file mode 100644 index 309582a..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/view/SensitiveContentPlugin.ets +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2026 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - -import SensitiveContentChannel, { SensitiveContentMethodHandler } from '../../embedding/engine/systemchannels/SensitiveContentChannel'; -import Log from '../../util/Log'; -import { BusinessError } from '@kit.BasicServicesKit'; -import window from '@ohos.window'; - -import { - SENSITIVE_CONTENT_SENSITIVITY, - NOT_SENSITIVE_CONTENT_SENSITIVITY, -} from '../../embedding/engine/systemchannels/SensitiveContentChannel'; -const TAG = "SensitiveContentChannel"; -export default class SensitiveContentPlugin implements SensitiveContentMethodHandler { - private readonly sensitiveContentChannel: SensitiveContentChannel; - private currentContentSensitivity: number = NOT_SENSITIVE_CONTENT_SENSITIVITY; - - constructor( - sensitiveContentChannel: SensitiveContentChannel - ) { - this.sensitiveContentChannel = sensitiveContentChannel; - this.sensitiveContentChannel.setSensitiveContentMethodHandler(this); - } - - setContentSensitivity(requestedContentSensitivity: number): void { - let isPrivacyMode: boolean; - switch (requestedContentSensitivity) { - case SENSITIVE_CONTENT_SENSITIVITY: - isPrivacyMode = true; - break; - case NOT_SENSITIVE_CONTENT_SENSITIVITY: - isPrivacyMode = false; - break; - default: - isPrivacyMode = false; - requestedContentSensitivity = NOT_SENSITIVE_CONTENT_SENSITIVITY; - break; - } - if (this.currentContentSensitivity === requestedContentSensitivity) { - // Content sensitivity for the requested View already set to requestedContentSensitivity. - return; - } - try { - window.getLastWindow(getContext(), (err: BusinessError, data) => { - const errCode = err.code; - if (errCode) { - return; - } - // Set requestedContentSensitivity on the View. - let promise = data.setWindowPrivacyMode(isPrivacyMode); - promise.then(() => { - Log.d(TAG, "success to set the window to privacy mode."); - this.currentContentSensitivity = requestedContentSensitivity; - }).catch((err: BusinessError) => { - Log.e(TAG, "Failed to set the window to privacy mode. Cause: " + JSON.stringify(err)); - }); - }) - } catch (exception) { - Log.e(TAG, "Exception when setting window privacy mode: " + JSON.stringify(exception)); - } - } - - getContentSensitivity(): number { - return this.currentContentSensitivity; - } - - isSupported(): boolean { - return true; - } - - destroy(): void { - this.sensitiveContentChannel.setSensitiveContentMethodHandler(null); - } -} - diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/ByteBuffer.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/ByteBuffer.ets deleted file mode 100644 index 0b644ba..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/ByteBuffer.ets +++ /dev/null @@ -1,809 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import util from '@ohos.util' -import StringUtils from './StringUtils' - -/** - * A byte buffer. - * - * Supports the following data types: - * - Bool - * - Int (8, 16, 32, 64) - * - Uint (8, 16, 32, 64) - * - BigInt (64) - * - String (utf8, utf16, and delimited) - * - TypedArray - * - */ -export class ByteBuffer { - /** - * Creates a byte buffer. - * @param source - The data source - * @param byteOffset - The byte offset - * @param byteLength - The byte length - * @returns A byte buffer - */ - static from(source: ArrayBuffer, byteOffset?: number, byteLength?: number): ByteBuffer { - const byteBuffer = new ByteBuffer() - byteBuffer.dataView = byteLength === undefined ? new DataView(source, byteOffset) : - new DataView(source, byteOffset, Math.min(source.byteLength, byteLength)) - byteBuffer.mByteOffset = byteBuffer.dataView.byteOffset - return byteBuffer - } - - private dataView?: DataView - /** The byte offset. */ - mByteOffset: number = 0 - - /** - * The byte offset. - * @returns The byte offset. - */ - get byteOffset(): number { - return this.mByteOffset - } - - /** - * Gets the byte length of the buffer. - * @returns The byte length - */ - get byteLength(): number { - return this.dataView?.byteLength ?? 0 - } - - /** - * The number of remaining bytes. - * @returns The number of bytes remaining. - */ - get bytesRemaining(): number { - return this.dataView ? this.dataView.byteLength - this.mByteOffset : 0; - } - - /** - * Checks if there are remaining bytes to read. - * @returns True if there are remaining bytes, false otherwise - */ - hasRemaining(): boolean { - return this.dataView != undefined && this.mByteOffset < this.dataView.byteLength; - } - - /** - * Gets the underlying ArrayBuffer up to the current offset. - * @returns The ArrayBuffer slice - */ - get buffer(): ArrayBuffer { - return this.dataView!.buffer.slice(0, this.mByteOffset) - } - - /** - * Skips the specified number of bytes. - * @param byteLength - The number of bytes to skip - */ - skip(byteLength: number): void { - this.mByteOffset += byteLength - } - - /** - * Resets the byte offset. - */ - reset(): void { - this.mByteOffset = this.dataView?.byteOffset ?? 0 - } - - /** - * Clears the byte buffer. - */ - clear(): void { - this.getUint8Array(0).fill(0) - } - - /** - * check buffer capacity. - */ - checkWriteCapacity(slen: number): void { - if (this.mByteOffset + slen > this.dataView!.byteLength) { - let newCapacity = this.dataView!.byteLength + (this.dataView!.byteLength >> 1); - if (newCapacity < this.dataView!.byteLength + slen + 512) { - newCapacity = this.dataView!.byteLength + slen + 512; - } - let newBuffer = new ArrayBuffer(newCapacity); - let newDataView = new DataView(newBuffer); - let oldUint8Array = new Uint8Array(this.dataView!.buffer); - let newUint8Array = new Uint8Array(newBuffer); - newUint8Array.set(oldUint8Array); - this.dataView = newDataView; - } - } - - /** - * Gets a boolean. - * @param byteOffset - The byte offset - */ - getBool(byteOffset: number): boolean { - return this.getInt8(byteOffset) !== 0 - } - - /** - * Reads the next boolean. - */ - readBool(): boolean { - return this.getInt8(this.mByteOffset++) !== 0 - } - - /** - * Sets a boolean. - * @param byteOffset - The byte offset - * @param value - The value - */ - setBool(byteOffset: number, value: boolean): void { - this.dataView?.setInt8(byteOffset, value ? 1 : 0) - } - - /** - * Writes the next boolean. - * @param value - The value - */ - writeBool(value: boolean): void { - this.checkWriteCapacity(1) - this.setInt8(this.mByteOffset++, value ? 1 : 0) - } - - /** - * Gets an signed byte. - * @param byteOffset - The byte offset - * @returns The value. - */ - getInt8(byteOffset: number): number { - return this.dataView?.getInt8(byteOffset) || 0 - } - - /** - * Reads the next signed byte. - * @returns The value. - */ - readInt8(): number { - return this.getInt8(this.mByteOffset++) - } - - /** - * Sets a signed byte. - * @param byteOffset - The byte offset - * @param value - The value - */ - setInt8(byteOffset: number, value: number): void { - this.dataView?.setInt8(byteOffset, value) - } - - /** - * Writes the next signed byte. - * @param value - The value - */ - writeInt8(value: number): void { - this.checkWriteCapacity(1) - this.setInt8(this.mByteOffset++, value) - } - - /** - * Gets an unsigned byte. - * @param byteOffset - The byte offset - * @returns The value. - */ - getUint8(byteOffset: number): number { - return this.dataView?.getUint8(byteOffset) || 0 - } - - /** - * Reads the next unsigned byte. - * @returns The value. - */ - readUint8(): number { - return this.getUint8(this.mByteOffset++) - } - - /** - * Sets an unsigned byte. - * @param byteOffset - The byte offset - * @param value - The value - */ - setUint8(byteOffset: number, value: number): void { - this.dataView?.setUint8(byteOffset, value) - } - - /** - * Writes the next signed byte. - * @param value - The value - */ - writeUint8(value: number): void { - this.checkWriteCapacity(1) - this.setUint8(this.mByteOffset++, value) - } - - /** - * Gets an signed short. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getInt16(byteOffset: number, littleEndian?: boolean): number { - return this.dataView?.getInt16(byteOffset, littleEndian) || 0 - } - - /** - * Reads the next signed short. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readInt16(littleEndian?: boolean): number { - const value = this.getInt16(this.mByteOffset, littleEndian) - this.mByteOffset += 2 - return value - } - - /** - * Sets a signed short. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setInt16(byteOffset: number, value: number, littleEndian?: boolean): void { - this.dataView?.setInt16(byteOffset, value, littleEndian) - } - - /** - * Writes the next signed short. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeInt16(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(2) - this.setInt16(this.mByteOffset, value, littleEndian) - this.mByteOffset += 2 - } - - /** - * Gets an unsigned short. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getUint16(byteOffset: number, littleEndian?: boolean): number { - return this.dataView?.getUint16(byteOffset, littleEndian) || 0 - } - - /** - * Reads the next unsigned short. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readUint16(littleEndian?: boolean): number { - const value = this.getUint16(this.mByteOffset, littleEndian) - this.mByteOffset += 2 - return value - } - - /** - * Sets an unsigned short. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setUint16(byteOffset: number, value: number, littleEndian?: boolean): void { - this.dataView?.setUint16(byteOffset, value, littleEndian) - } - - /** - * Writes the next signed short. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeUint16(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(2) - this.setUint16(this.mByteOffset, value, littleEndian) - this.mByteOffset += 2 - } - - /** - * Gets an signed integer. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getInt32(byteOffset: number, littleEndian?: boolean): number { - return this.dataView?.getInt32(byteOffset, littleEndian) ?? 0 - } - - /** - * Reads the next signed integer. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readInt32(littleEndian?: boolean): number { - const value = this.getInt32(this.mByteOffset, littleEndian) - this.mByteOffset += 4 - return value - } - - /** - * Sets a signed integer. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setInt32(byteOffset: number, value: number, littleEndian?: boolean): void { - this.dataView?.setInt32(byteOffset, value, littleEndian) - } - - /** - * Writes the next signed integer. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeInt32(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(4) - this.setInt32(this.mByteOffset, value, littleEndian) - this.mByteOffset += 4 - } - - /** - * Gets an unsigned integer. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getUint32(byteOffset: number, littleEndian?: boolean): number { - return this.dataView?.getUint32(byteOffset, littleEndian) ?? 0 - } - - /** - * Reads the next unsigned integer. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readUint32(littleEndian?: boolean): number { - const value = this.getUint32(this.mByteOffset, littleEndian) - this.mByteOffset += 4 - return value - } - - /** - * Sets an unsigned integer. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setUint32(byteOffset: number, value: number, littleEndian?: boolean): void { - this.dataView?.setUint32(byteOffset, value, littleEndian) - } - - /** - * Writes the next signed integer. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeUint32(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(4) - this.setUint32(this.mByteOffset, value, littleEndian) - this.mByteOffset += 4 - } - - /** - * Gets a float. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getFloat32(byteOffset: number, littleEndian?: boolean): number { - return this.dataView?.getFloat32(byteOffset, littleEndian) ?? 0 - } - - /** - * Reads the next float. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readFloat32(littleEndian?: boolean): number { - const value = this.getFloat32(this.mByteOffset, littleEndian) - this.mByteOffset += 4 - return value - } - - /** - * Sets a float. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setFloat32(byteOffset: number, value: number, littleEndian?: boolean): void { - this.dataView?.setFloat32(byteOffset, value, littleEndian) - } - - /** - * Writes the next float. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeFloat32(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(4) - this.setFloat32(this.mByteOffset, value, littleEndian) - this.mByteOffset += 4 - } - - /** - * Gets a double. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getFloat64(byteOffset: number, littleEndian?: boolean): number { - return this.dataView?.getFloat64(byteOffset, littleEndian) ?? 0 - } - - /** - * Reads the next double. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readFloat64(littleEndian?: boolean): number { - const value = this.getFloat64(this.mByteOffset, littleEndian) - this.mByteOffset += 8 - return value - } - - /** - * Sets a double. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setFloat64(byteOffset: number, value: number, littleEndian?: boolean): void { - this.dataView?.setFloat64(byteOffset, value, littleEndian) - } - - /** - * Writes the next double. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeFloat64(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(8) - this.setFloat64(this.mByteOffset, value, littleEndian) - this.mByteOffset += 8 - } - - /** - * Gets an signed long. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getBigInt64(byteOffset: number, littleEndian?: boolean): bigint { - return this.dataView?.getBigInt64(byteOffset, littleEndian) ?? BigInt(0) - } - - /** - * Reads the next signed long. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readBigInt64(littleEndian?: boolean): bigint { - const value = this.getBigInt64(this.mByteOffset, littleEndian) - this.mByteOffset += 8 - return value - } - - /** - * Sets a signed long. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setBigInt64(byteOffset: number, value: bigint, littleEndian?: boolean): void { - this.dataView?.setBigInt64(byteOffset, value, littleEndian) - } - - /** - * Writes the next signed long. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeBigInt64(value: bigint, littleEndian?: boolean): void { - this.checkWriteCapacity(8) - this.setBigInt64(this.mByteOffset, value, littleEndian) - this.mByteOffset += 8 - } - - /** - * Gets an unsigned long. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getBigUint64(byteOffset: number, littleEndian?: boolean): bigint { - return this.dataView?.getBigUint64(byteOffset, littleEndian) ?? BigInt(0) - } - - /** - * Reads the next unsigned long. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readBigUint64(littleEndian?: boolean): bigint { - const value = this.getBigUint64(this.mByteOffset, littleEndian) - this.mByteOffset += 8 - return value - } - - /** - * Sets an unsigned long. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setBigUint64(byteOffset: number, value: bigint, littleEndian?: boolean): void { - this.dataView?.setBigUint64(byteOffset, value, littleEndian) - } - - /** - * Writes the next unsigned long. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeBigUint64(value: bigint, littleEndian?: boolean): void { - this.checkWriteCapacity(8) - this.setBigUint64(this.mByteOffset, value, littleEndian) - this.mByteOffset += 8 - } - - /** - * Gets an signed long. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getInt64(byteOffset: number, littleEndian?: boolean): bigint { - return this.getBigInt64(byteOffset, littleEndian) - } - - /** - * Reads the next signed long. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readInt64(littleEndian?: boolean): bigint { - const value = this.getInt64(this.mByteOffset, littleEndian) - this.mByteOffset += 8 - return value - } - - /** - * Sets a signed long. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setInt64(byteOffset: number, value: number, littleEndian?: boolean): void { - this.setBigInt64(byteOffset, BigInt(value), littleEndian) - } - - /** - * Writes the next signed long. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeInt64(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(8) - this.setInt64(this.mByteOffset, value, littleEndian) - this.mByteOffset += 8 - } - - /** - * Gets an unsigned long. - * @param byteOffset - The byte offset - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - getUint64(byteOffset: number, littleEndian?: boolean): number { - return Number(this.getBigUint64(byteOffset, littleEndian)) - } - - /** - * Reads the next unsigned long. - * @param littleEndian - Whether the value is little endian - * @returns The value. - */ - readUint64(littleEndian?: boolean): number { - const value = this.getUint64(this.mByteOffset, littleEndian) - this.mByteOffset += 8 - return value - } - - /** - * Sets an unsigned long. - * @param byteOffset - The byte offset - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - setUint64(byteOffset: number, value: number, littleEndian?: boolean): void { - this.setBigUint64(byteOffset, BigInt(value), littleEndian) - } - - /** - * Writes the next signed long. - * @param value - The value - * @param littleEndian - Whether the value is little endian - */ - writeUint64(value: number, littleEndian?: boolean): void { - this.checkWriteCapacity(8) - this.setUint64(this.mByteOffset, value, littleEndian) - this.mByteOffset += 8 - } - - /** - * Gets an array of unsigned bytes. - * @param byteOffset - The byte offset - * @param byteLength - The byte length - * @returns The value. - */ - getUint8Array(byteOffset: number, byteLength?: number): Uint8Array { - return this.dataView == null ? - new Uint8Array(StringUtils.stringToArrayBuffer(""), byteOffset, byteLength) : - new Uint8Array(this.dataView?.buffer, this.dataView?.byteOffset + byteOffset, byteLength) - } - - /** - * Reads the next array of unsigned bytes. - * @param byteLength - The byte length - * @returns The value. - */ - readUint8Array(byteLength?: number): Uint8Array { - const value = this.getUint8Array(this.mByteOffset, byteLength) - this.mByteOffset += value.byteLength - return value - } - - /** - * Sets an array of unsigned bytes. - * @param byteOffset - The byte offset - * @param value - The value - */ - setUint8Array(byteOffset: number, value: Uint8Array): void { - const byteLength = value.byteLength - this.getUint8Array(byteOffset, byteLength).set(value) - } - - /** - * Writes the next array of unsigned bytes. - * @param value - The value - */ - writeUint8Array(value: Uint8Array): void { - this.checkWriteCapacity(value.byteLength) - this.setUint8Array(this.mByteOffset, value) - this.mByteOffset += value.byteLength - } - - /** - * Gets an array of unsigned shorts. - * @param byteOffset - The byte offset - * @param byteLength - The byte length - * @returns The value. - */ - getUint16Array(byteOffset: number, byteLength?: number): Uint16Array { - if (byteLength !== undefined) { - byteLength = Math.floor(byteLength / 2) - } - return this.dataView == null ? - new Uint16Array(StringUtils.stringToArrayBuffer(""), byteOffset, byteLength) : - new Uint16Array(this.dataView.buffer, this.dataView.byteOffset + byteOffset, byteLength) - } - - /** - * Reads the next array of unsigned shorts. - * @param byteLength - The byte length - * @returns The value. - */ - readUint16Array(byteLength?: number): Uint16Array { - const value = this.getUint16Array(this.mByteOffset, byteLength) - this.mByteOffset += value.byteLength - return value - } - - /** - * Sets an array of unsigned bytes. - * @param byteOffset - The byte offset - * @param value - The value - */ - setUint16Array(byteOffset: number, value: Uint16Array): void { - const byteLength = value.byteLength - this.getUint16Array(byteOffset, byteLength).set(value) - } - - /** - * Writes the next array of unsigned bytes. - * @param value - The value - */ - writeUint16Array(value: Uint16Array): void { - this.checkWriteCapacity(value.byteLength) - this.setUint16Array(this.mByteOffset, value) - this.mByteOffset += value.byteLength - } - - /** - * Gets a string. - * @param byteOffset - The byte offset - * @param byteLength - The byte length - * @param byteEncoding - The byte encoding - * @returns The value. - */ - getString(byteOffset: number, byteLength?: number, byteEncoding?: string): string { - const decoder = new util.TextDecoder(byteEncoding || "utf-8") - const encoded = this.getUint8Array(byteOffset, byteLength) - return decoder.decode(encoded) - } - - /** - * Reads the next string. - * @param byteLength - The byte length - * @param byteEncoding - The byte encoding - * @returns The value. - */ - readString(byteLength?: number, byteEncoding?: string): string { - const value = this.getString(this.mByteOffset, byteLength, byteEncoding) - if (byteLength === undefined) { - this.mByteOffset = this.dataView?.byteLength ?? 0 - } else { - this.mByteOffset += byteLength - } - return value - } - - /** - * Sets a string. - * @param byteOffset - The byte offset - * @param value - The string value - * @param byteEncoding - The byte encoding - * @returns The byte length. - */ - setString(byteOffset: number, value: string, byteEncoding?: string, write?: boolean): number { - if (byteEncoding && byteEncoding !== "utf-8") { - throw new TypeError("String encoding '" + byteEncoding + "' is not supported") - } - const encoder = new util.TextEncoder() - const byteLength = Math.min(this.dataView!.byteLength - byteOffset, value.length * 4) - if (write) { - this.checkWriteCapacity(byteLength) - } - const destination = this.getUint8Array(byteOffset, byteLength) - const written = encoder.encodeInto(value, destination).written - return written || 0 - } - - /** - * Writes the next a string. - * @param value - The string value - * @param byteEncoding - The byte encoding - */ - writeString(value: string, byteEncoding?: string): void { - const byteLength = this.setString(this.mByteOffset, value, byteEncoding, true) - this.mByteOffset += byteLength - } - - /** - * Formats to a string. - * @param format - The string format - * @returns The string. - */ - toString(format?: string): string { - return [...this.getUint8Array(0)].map((byte: number) => { - switch (format) { - case "hex": - return ("00" + byte.toString(16)).slice(-2) - default: - return byte.toString(10) - } - }).join(" ") - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/Json5ToJson.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/Json5ToJson.ets deleted file mode 100644 index cb4d79c..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/Json5ToJson.ets +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2025 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ - - -/** - * Remove JSON5-style comments from text. - * Supports: // line comments, /* block comments *\/ - * Preserves anything inside string literals: "..." (and optionally '...' if present) - */ - -//Pass in a string and remove the comments from it -function stripComments(input: string) { - //Return value - let out = ''; - let i = 0; - - const len = input.length; - let inString = false; - let stringQuote = ''; - //Whether it is in an escape state (the previous character is \), - //used to handle \" or \' etc. - let escaped = false; - - //Loop through each character - while (i < len) { - const ch = input[i]; - const next = i + 1 < len ? input[i + 1] : ''; - - //If currently inside a string, output everything exactly as it is - if (inString) { - out += ch; - //If the previous character is a backslash causing this character to be escaped, - //then clear the escape state - if (escaped) { - escaped = false; - //When encountering a backslash, enter escape mode - } else if (ch === '\\') { - escaped = true; - } else if (ch === stringQuote) { - inString = false; - stringQuote = ''; - } - i++; - continue; - } - //Not inside a string; - //enter string mode when encountering a quotation mark - if (ch === '"' || ch === "'") { - inString = true; - stringQuote = ch; - out += ch; - i++; - continue; - } - //Detect and skip single-line comments - if (ch === '/' && next === '/') { - i += 2; - while (i < len && input[i] !== '\n' && input[i] !== '\r') i++; - continue; - } - //Detect and skip block comments - if (ch === '/' && next === '*') { - i += 2; - while (i < len) { - if (input[i] === '*' && i + 1 < len && input[i + 1] === '/') { - i += 2; - break; - } - i++; - } - continue; - } - out += ch; - i++; - } - return out; -} - - -export function json5Tojson(json5Text: string): string { - const noComments = stripComments(json5Text); - return noComments; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/Log.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/Log.ets deleted file mode 100644 index d8b0dfc..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/Log.ets +++ /dev/null @@ -1,119 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import HiLog from '@ohos.hilog'; -import BuildProfile from '../../../../BuildProfile'; - -const DOMAIN: number = 0x00FF; -const TAG = "Flutter"; -const SYMBOL = " --> "; - -/** - * Basic log class - */ -export default class Log { - private static _logLevel = HiLog.LogLevel.WARN; - - /** - * Sets the log level. - * - * @param level - The log level - */ - public static setLogLevel(level: HiLog.LogLevel) { - Log._logLevel = level; - } - - /** - * Outputs debug-level logs. - * - * @param tag - The log tag - * @param format - The log format string - * @param args - The log parameters - * @since 7 - */ - static d(tag: string, format: string, ...args: Object[]) { - if (Log.isLoggable(HiLog.LogLevel.DEBUG)) { - HiLog.debug(DOMAIN, TAG, tag + SYMBOL + format, args); - } - } - - /** - * Outputs info-level logs. - * - * @param tag - The log tag - * @param format - The log format string - * @param args - The log parameters - * @since 7 - */ - static i(tag: string, format: string, ...args: Object[]) { - if (Log.isLoggable(HiLog.LogLevel.INFO)) { - HiLog.info(DOMAIN, TAG, tag + SYMBOL + format, args); - } - } - - /** - * Outputs warning-level logs. - * - * @param tag - The log tag - * @param format - The log format string - * @param args - The log parameters - * @since 7 - */ - static w(tag: string, format: string, ...args: Object[]) { - if (Log.isLoggable(HiLog.LogLevel.WARN)) { - HiLog.warn(DOMAIN, TAG, tag + SYMBOL + format, args); - } - } - - /** - * Outputs error-level logs. - * - * @param tag - The log tag - * @param format - The log format string - * @param args - The log parameters - * @since 7 - */ - static e(tag: string, format: string, ...args: Object[]) { - if (Log.isLoggable(HiLog.LogLevel.ERROR)) { - args.forEach((item: Object, index: number) => { - if (item instanceof Error) { - args[index] = item.message + item.stack; - } - format += "%{public}s"; - }) - HiLog.error(DOMAIN, TAG, tag + SYMBOL + format, args); - } - } - - /** - * Outputs fatal-level logs. - * - * @param tag - The log tag - * @param format - The log format string - * @param args - The log parameters - * @since 7 - */ - static f(tag: string, format: string, ...args: Object[]) { - if (Log.isLoggable(HiLog.LogLevel.FATAL)) { - HiLog.fatal(DOMAIN, TAG, tag + SYMBOL + format, args); - } - } - - /** - * Checks whether logs of the specified tag and level can be printed. - * - * @param level - The log level - * @returns True if logs can be printed, false otherwise - * @since 7 - */ - private static isLoggable(level: HiLog.LogLevel): boolean { - let buildModeName: string = BuildProfile.BUILD_MODE_NAME.toLowerCase(); - if (buildModeName == 'release' || buildModeName == 'profile') { - return level >= Log._logLevel && HiLog.isLoggable(DOMAIN, TAG, level); - } - return HiLog.isLoggable(DOMAIN, TAG, level); - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/MessageChannelUtils.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/MessageChannelUtils.ets deleted file mode 100644 index f08b175..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/MessageChannelUtils.ets +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import BasicMessageChannel from '../plugin/common/BasicMessageChannel'; -import { BinaryMessenger } from '../plugin/common/BinaryMessenger'; -import StringUtils from './StringUtils'; - -/** - * Utility class for message channel operations. - */ -export default class MessageChannelUtils { - /** - * Resizes a channel buffer. - * @param messenger - The BinaryMessenger to use - * @param channel - The channel name - * @param newSize - The new buffer size - */ - static resizeChannelBuffer(messenger: BinaryMessenger, channel: string, newSize: number) { - const dataStr = `resize\r${channel}\r${newSize}` - messenger.send(BasicMessageChannel.CHANNEL_BUFFERS_CHANNEL, StringUtils.stringToArrayBuffer(dataStr)); - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/PasteboardUtils.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/PasteboardUtils.ets deleted file mode 100644 index cf44eeb..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/PasteboardUtils.ets +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ -import { BusinessError, pasteboard } from "@kit.BasicServicesKit"; -import { abilityAccessCtrl } from "@kit.AbilityKit"; -import FlutterManager from "../embedding/ohos/FlutterManager"; -import Log from "./Log"; - -/** - * Utility class for pasteboard operations. - * Provides methods for copying and pasting data to/from the system pasteboard. - */ -export class PasteboardUtils { - private static TAG = "PasteboardUtils"; - - /** - * Copies text data to the OpenHarmony pasteboard. - * @param text - The text to copy - */ - static setCopyData(text: string) { - const pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); - const systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); - try { - systemPasteboard.setDataSync(pasteData); - } catch (err) { - Log.w(PasteboardUtils.TAG, "Failed to set PasteData. Cause: " + JSON.stringify(err)); - } - } - - /** - * Gets paste data from the OpenHarmony pasteboard asynchronously. - * Requests READ_PASTEBOARD permission if needed. - * @returns A Promise that resolves to the pasted text, or empty string if no text is available. - */ - static getPasteDataAsync(): Promise { - return new Promise((resolve) => { - let atManager = abilityAccessCtrl.createAtManager(); - // request the ohos paste operation authority - atManager.requestPermissionsFromUser(FlutterManager.getInstance().getUIAbility().context, - ['ohos.permission.READ_PASTEBOARD']).then((data) => { - enum AuthResultStatus { - NOT_CONFIGURED = -1, - GRANTED = 0, - INVALID_REQ = 2 - } - - const authResult: number = data.authResults[0]; - switch (authResult) { - case AuthResultStatus.GRANTED: { - let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard(); - systemPasteboard.getData().then(async (pasteData: pasteboard.PasteData) => { - let pasteText: string = ''; - const recordCount: number = pasteData.getRecordCount(); - for (let i = 0; i < recordCount; i++) { - const record = pasteData.getRecord(i); - let text: string = ''; - if (typeof record.getValidTypes === 'function') { - // For api14 and above, click here. More formats are supported - text = await PasteboardUtils.getTargetTypesData(record); - } else if (record.mimeType === pasteboard.MIMETYPE_TEXT_HTML) { - const htmlText: StyledString = await StyledString.fromHtml(record.htmlText); - text = htmlText.getString(); - } else if (record.mimeType === pasteboard.MIMETYPE_TEXT_PLAIN) { - text = record.plainText; - } - pasteText += text; - } - resolve(pasteText); - }).catch((err: BusinessError) => { - Log.e(PasteboardUtils.TAG, "Failed to get PasteData. Cause: " + JSON.stringify(err)); - }); - break; - } - case AuthResultStatus.NOT_CONFIGURED: - case AuthResultStatus.INVALID_REQ: - default: { - Log.e(PasteboardUtils.TAG, "error code: " + authResult); - break; - } - } - }).catch((err: BusinessError) => { - Log.e(PasteboardUtils.TAG, "Failed to request permissions from user. Cause: " + JSON.stringify(err)); - }) - }); - } - - static async getTargetTypesData(record: pasteboard.PasteDataRecord): Promise { - let targetTypes: string[] = [ - pasteboard.MIMETYPE_TEXT_PLAIN, - pasteboard.MIMETYPE_TEXT_HTML - ]; - let tmpTypes: string[] = record.getValidTypes(targetTypes); - let str: string = ''; - for (let j = 0; j < tmpTypes.length; j++) { - let value = ''; - try { - value = await record.getData(tmpTypes[j]) as string; - } catch (error) { - Log.e(PasteboardUtils.TAG, "Failed to record.getData: " + JSON.stringify(error)); - } - if (value) { - if (tmpTypes[j] === pasteboard.MIMETYPE_TEXT_HTML) { - try { - const htmlText: StyledString = await StyledString.fromHtml(value); - str = htmlText.getString(); - } catch (error) { - Log.e(PasteboardUtils.TAG, "Failed to record.getData: " + JSON.stringify(error)); - } - } else { - str = value - } - break; - } - } - return str; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/PathUtils.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/PathUtils.ets deleted file mode 100644 index 629ff39..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/PathUtils.ets +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import common from '@ohos.app.ability.common'; -import fs from '@ohos.file.fs'; -import Log from './Log'; - -const TAG: string = "PathUtils"; - -/** - * Utility class for path operations in OpenHarmony. - * Provides methods for getting application directories. - */ -export default class PathUtils { - /** - * Gets the files directory for the application. - * @param context - The application context - * @returns The files directory path. - */ - static getFilesDir(context: common.Context): string { - return context.filesDir; - } - - /** - * Gets the cache directory for the application. - * @param context - The application context - * @returns The cache directory path. - */ - static getCacheDirectory(context: common.Context): string { - return context.cacheDir; - } - - /** - * Gets or creates the Flutter data directory. - * @param context - The application context - * @returns The Flutter data directory path, or null if creation fails. - */ - static getDataDirectory(context: common.Context): string | null { - const name = "flutter"; - const flutterDir = context.filesDir + "/" + name; - if (!fs.accessSync(flutterDir)) { - try { - fs.mkdirSync(flutterDir); - } catch (err) { - Log.e(TAG, "mkdirSync failed err:" + err); - return null; - } - } - return flutterDir; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/StringUtils.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/StringUtils.ets deleted file mode 100644 index 9bd3b4d..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/StringUtils.ets +++ /dev/null @@ -1,68 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import flutter from 'libflutter.so' - -/** - * Utility class for string operations. - * Provides methods for converting between strings and ArrayBuffer/Uint8Array. - */ -export default class StringUtils { - - /** - * Converts a string to an ArrayBuffer using UTF-8 encoding. - * @param str - The string to convert. - * @returns The ArrayBuffer containing the UTF-8 encoded string. - */ - static stringToArrayBuffer(str: string): ArrayBuffer { - if (str.length == 0) { - return new ArrayBuffer(0); - } - return flutter.nativeEncodeUtf8(str).buffer; - } - - /** - * Converts an ArrayBuffer to a string using UTF-8 decoding. - * @param buffer - The ArrayBuffer to convert. - * @returns The decoded string, or empty string if buffer is empty. - */ - static arrayBufferToString(buffer: ArrayBuffer): string { - if (buffer.byteLength <= 0) { - return ""; - } - return flutter.nativeDecodeUtf8(new Uint8Array(buffer)); - } - - /** - * Converts a Uint8Array to a string using UTF-8 decoding. - * @param buffer - The Uint8Array to convert. - * @returns The decoded string, or empty string if buffer is empty. - */ - static uint8ArrayToString(buffer: Uint8Array): string { - if (buffer.length <= 0) { - return ""; - } - return flutter.nativeDecodeUtf8(buffer); - } - - /** - * Checks if a string is not empty. - * @param str - The string to check. - * @returns True if the string is not null and has length > 0, false otherwise. - */ - static isNotEmpty(str: string): boolean { - return str != null && str.length > 0; - } - - /** - * Checks if a string is empty. - * @param str - The string to check - * @returns True if the string is null, undefined, or has length 0, false otherwise. - */ - static isEmpty(str: string): boolean { - return (!str) || str.length == 0; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/ToolUtils.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/ToolUtils.ets deleted file mode 100644 index 292c0aa..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/ToolUtils.ets +++ /dev/null @@ -1,30 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ -import Any from '../plugin/common/Any'; - -/** - * Utility class for object operations. - */ -export default class ToolUtils { - /** - * Checks if a value is an object. - * @param object - The value to check - * @returns True if the value is an object, false otherwise. - */ - static isObj(object: Object): boolean { - return object && typeof (object) == 'object'; - } - - /** - * Checks if an object implements a specific method (interface check). - * @param obj - The object to check - * @param method - The method name to check for - * @returns True if the object has the method and it's a function, false otherwise. - */ - static implementsInterface(obj: Any, method: string): boolean { - return Reflect.has(obj, method) && typeof obj[method] === 'function' - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/TraceSection.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/TraceSection.ets deleted file mode 100644 index f7c1f91..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/util/TraceSection.ets +++ /dev/null @@ -1,59 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on TraceSection.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import hiTraceMeter from '@ohos.hiTraceMeter' - -/** - * Utility class for tracing code sections using hiTraceMeter. - * Provides methods to begin and end trace sections with automatic name cropping. - */ -export class TraceSection { - /** The current task ID for trace sections. */ - static taskId: number = 0; - - /** - * Crops a section name to ensure it stays below 124 characters. - * @param sectionName - The section name to crop - * @returns The cropped section name. - * @private - */ - private static cropSectionName(sectionName: string): string { - return sectionName.length < 124 ? sectionName : sectionName.substring(0, 124) + "..."; - } - - /** - * Wraps Trace.beginSection to ensure that the line length stays below 127 code units. - * - * @param sectionName - The string to display as the section name in the trace - * @returns The task ID for this trace section - */ - public static begin(sectionName: string): number { - TraceSection.taskId++; - hiTraceMeter.startTrace(TraceSection.cropSectionName(sectionName), TraceSection.taskId); - return TraceSection.taskId; - } - - /** - * Ends a trace section. - * @param sectionName - The section name to end. - */ - public static end(sectionName: string): void { - hiTraceMeter.finishTrace(TraceSection.cropSectionName(sectionName), TraceSection.taskId); - } - - /** - * Ends a trace section with a specific task ID. - * @param sectionName - The section name to end. - * @param id - The task ID to use. - */ - public static endWithId(sectionName: string, id: number): void { - hiTraceMeter.finishTrace(TraceSection.cropSectionName(sectionName), id); - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicView.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicView.ets deleted file mode 100644 index a757281..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicView.ets +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ -import matrix4 from '@ohos.matrix4'; -import Any from '../../plugin/common/Any'; -import Log from '../../util/Log'; - -/** - * Dynamic View creation - * from a recursive data structure - * - * exported @Component: DynamicView - * exported view model classes: - * - DVModelContainer - * - DVModel - * - DVModelParameters - * - DVModelEvents - * - DVModelChildren - * - * The purpose of exporting the DVModel classes - * is to make them available to the converter from - * JD's XML format and the expression parser. These - * components are expected to generate and update the - * DVModel. - * - * An application written by JS should only import - * DynamicView, DVModelContainer to be used in their own ArkUI - * container view. - */ - -/** - * View Model classes - */ - -/** - * Parameters for DynamicView model components. - * This class is observed to enable reactive updates when parameters change. - * It serves as a container for component attributes and properties. - */ -@Observed -export class DVModelParameters extends Object { - /* empty, just get any instance wrapped inside an ObservedObject - with the help of the decoration */ -} - -/** - * Events for DynamicView model components. - * This class is observed to enable reactive updates when events change. - * It serves as a container for component event handlers. - */ -@Observed -export class DVModelEvents extends Object { - /* empty, just get any instance wrapped inside an ObservedObject - with the help of the decoration */ -} - -/** - * Children array for DynamicView model components. - * This class is observed to enable reactive updates when children change. - * It serves as a container for child DVModel instances. - */ -@Observed -export class DVModelChildren extends Array { - /* empty, just get any instance wrapped inside an ObservedObject - with the help of the decoration */ -} - -let nextId: number = 1; - -/** - * Model class representing a dynamic view component. - * This class holds all information needed to render a component dynamically, - * including its type, parameters, events, children, and optional builder. - */ -@Observed -export class DVModel { - /** The unique identifier for this model. */ - id_: number; - /** The component type (e.g., "Column", "Row", "Text", "Image"). */ - compType: string; - /** The component parameters. */ - params: DVModelParameters; - /** The component events. */ - events: DVModelEvents; - /** The child components. */ - children: DVModelChildren; - /** Optional custom builder function. */ - builder: Any; - - /** - * Constructs a new DVModel instance. - * @param compType - The component type (e.g., "Column", "Row", "Text", "Image") - * @param params - The component parameters - * @param events - The component events - * @param children - The child components - * @param builder - Optional custom builder function - */ - constructor(compType: string, params: DVModelParameters, events: DVModelEvents, children: DVModelChildren, - builder?: Any) { - this.id_ = nextId++; - this.compType = compType; - this.params = params ?? new DVModelParameters; - this.events = events; - this.children = children; - this.builder = builder; - } - - /** - * Gets the layout parameters for this model. - * @returns The DVModelParameters instance - */ - public getLayoutParams(): DVModelParameters { - return this.params; - } -} - -/** - * Container for the root DVModel object. - * This class wraps the root model to provide a container structure. - */ -export class DVModelContainer { - /** The root DVModel instance. */ - model: DVModel; - - /** - * Constructs a new DVModelContainer instance. - * @param model - The root DVModel to contain - */ - constructor(model: DVModel) { - this.model = model; - } -} - -/** - DynamicView is the @Component that does all the work: - - The following 4 features are the key solution elements for dynamic View - construction and update: - - 1. The if statement decides which framework component to create. - We can not use a factory function here, because that would requite calling - a regular function inside build() or a @Builder function. - - 2. Take note of the @Builder for Row, Column containers: - These functions create DynamicView Views inside a DynamicView - view. This behaviour is why we talk about DynamicView as a 'recursive' View. - All @Builder functions are member functions of the DynamicView @Component to - retain access ('this.xyz') to its decorated state variables. - - 3. The @Extend functions execute attribute and event handler registration functions - for all attributes and events permissable on the framework component, irrespective - if DVModelParameters or DVModelEvents objects includes a value or not. If not - the attribute or event is set to 'undefined' by intention. This is required to unset - any previously set value. - - 4. The scope ('this') of any lambda registered as an event hander function, e.g. for onClick, - is the @Component, in which the DVModel object is initialized. This said, it is advised to initialize - the DVModel object in the @Component that is parent to outmost DynamicView. Thereby, - any event handler function is able to mutate decorated state variables of that @Component - - */ - -@Component -export struct DynamicView { - @ObjectLink model: DVModel; - @ObjectLink children: DVModelChildren; - @ObjectLink params: DVModelParameters; - @ObjectLink events: DVModelEvents; - @BuilderParam customBuilder?: ($$: BuilderParams) => void; - - /** - * Gets a parameter value from the parameters object. - * @param params - The parameters object - * @param element - The parameter key - * @returns The parameter value, or undefined if not found - */ - getParams: (params: DVModelParameters, element: string) => string | Any = - (params: DVModelParameters, element: string): string | Any => { - let params2 = params as Record; - return params2[element]; - } - - /** - * Gets an event handler from the events object. - * @param events - The events object - * @param element - The event key - * @returns The event handler, or undefined if not found - */ - getEvents: (events: DVModelEvents, element: string) => Any = (events: DVModelEvents, element: string): Any => { - let events2 = events as Record; - return events2[element]; - } - - @Styles - common_attrs() { - .width(this.getParams(this.params, "width")) - .height(this.getParams(this.params, "height")) - .backgroundColor(this.getParams(this.params, "backgroundColor")) - .onClick(this.getEvents(this.events, "onClick")) - .margin({ - left: this.getParams(this.params, "marginLeft"), - right: this.getParams(this.params, "marginRight"), - top: this.getParams(this.params, "marginTop"), - bottom: this.getParams(this.params, "marginBottom") - }) - .onTouch(this.getEvents(this.events, "onTouch")) - .onFocus(this.getEvents(this.events, "onFocus")) - .onBlur(this.getEvents(this.events, "onBlur")) - .translate({ - x: this.getParams(this.params, "translateX"), - y: this.getParams(this.params, "translateY"), - z: this.getParams(this.params, "translateZ") - }) - .transform(this.getParams(this.params, "matrix")) - .direction(this.getParams(this.params, "direction")) - } - - @Styles - clip_attrs() { - .clip(this.getParams(this.params, "rectWidth") ? new Rect({ - width: this.getParams(this.params, "rectWidth"), - height: this.getParams(this.params, "rectHeight"), - radius: this.getParams(this.params, "rectRadius") - }) : null) - .clip(this.getParams(this.params, "pathWidth") ? new Path({ - width: this.getParams(this.params, "pathWidth"), - height: this.getParams(this.params, "pathHeight"), - commands: this.getParams(this.params, "pathCommands") - }) : null) - } - - @Builder - buildChildren() { - ForEach(this.children, - (child: Any) => { - DynamicView({ - model: child as DVModel, - params: child.params, - events: child.events, - children: child.children, - customBuilder: child.builder - }) - }, - (child: Any) => `${child.id_}` - ) - } - - @Builder - buildRow() { - Row() { - this.buildChildren() - } - .common_attrs() - .clip_attrs() - } - - @Builder - buildColumn() { - Column() { - this.buildChildren() - } - .common_attrs() - .clip_attrs() - } - - @Builder - buildStack() { - Stack() { - this.buildChildren() - } - .common_attrs() - .clip_attrs() - .alignContent(this.getParams(this.params, "alignContent")) - } - - @Builder - buildText() { - Text(`${this.getParams(this.params, "value")}`) - .common_attrs() - .fontColor(this.getParams(this.params, "fontColor")) - } - - @Builder - buildImage() { - Image(this.getParams(this.params, "src")) - .common_attrs() - } - - @Builder - buildButton() { - Button(this.getParams(this.params, "value")) - .common_attrs() - } - - @Builder - buildNodeContainer() { - NodeContainer(this.getParams(this.params, "nodeController")) - .common_attrs() - .position({ - x: (this.params as Record)['left'] as number, - y: (this.params as Record)['top'] as number - }) - } - - @Builder - buildCustom() { - if (this.customBuilder) { - this.customBuilder(new BuilderParams(this.params)); - } - } - - build() { - if (this.model.compType == "Column") { - this.buildColumn() - } else if (this.model.compType == "Row") { - this.buildRow() - } else if (this.model.compType == "Stack") { - this.buildStack() - } else if (this.model.compType == "Text") { - this.buildText() - } else if (this.model.compType == "Image") { - this.buildImage() - } else if (this.model.compType == "Button") { - this.buildButton() - } else if (this.model.compType == "NodeContainer") { - this.buildNodeContainer() - } else { - this.buildCustom() - } - } -} - -/** - * Parameters passed to custom builder functions. - * This class wraps DVModelParameters for use in custom builders. - */ -export class BuilderParams { - /** The DVModelParameters to wrap. */ - params: DVModelParameters; - - /** - * Constructs a new BuilderParams instance. - * @param params - The DVModelParameters to wrap - */ - constructor(params: DVModelParameters) { - this.params = params; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicViewJson.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicViewJson.ets deleted file mode 100644 index afc3412..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/DynamicView/dynamicViewJson.ets +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2021-2024 Huawei Device Co., Ltd. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE_HW file. - */ -import Any from '../../plugin/common/Any'; - -import { DVModel, DVModelParameters, DVModelEvents, DVModelChildren } from "./dynamicView"; -import Log from '../../util/Log'; -const TAG = "dynamicViewJson"; - -/** - * Creates a DVModel instance from a JSON object. - * This function recursively parses the JSON structure to build a complete DVModel tree. - * @param json - The JSON object representing the view structure - * @returns A DVModel instance created from the JSON - */ -export function createDVModelFromJson(json: Object): DVModel { - - /** - * Helper function to create children from a JSON array. - * @param children - Array of child JSON objects - * @returns A DVModelChildren instance containing parsed child models - * @private - */ - let createChildrenFrom: (children: Array) => DVModelChildren = (children: Array): DVModelChildren => { - let result = new DVModelChildren(); - if (Array.isArray(children)) { - (children as Array).forEach(child => { - const childView = createDVModelFromJson(child); - if (childView != undefined) { - result.push(childView); - } - }); - } - return result; - } - - /** - * Helper function to set a parameter or event value. - * @param result - The parameters or events object to modify - * @param key - The key to set - * @param element - The source object containing the value - * @private - */ - let setParams: (result: DVModelParameters | DVModelEvents, key: Any, element: Object) => void = - (result: DVModelParameters, key: Any, element: Any): void => { - let newResult = result as Record; - newResult[key] = element[key]; - } - - /** - * Helper function to create parameters from a JSON attributes object. - * @param attributes - The attributes JSON object - * @returns A DVModelParameters instance - * @private - */ - let createAttributesFrom: (attributes: Object) => DVModelParameters = (attributes: Object): DVModelParameters => { - let result = new DVModelParameters(); - if ((typeof attributes == "object") && (!Array.isArray(attributes))) { - Object.keys(attributes).forEach(k => { - setParams(result, k, attributes) - }); - } - return result; - } - - /** - * Helper function to create events from a JSON events object. - * @param events - The events JSON object - * @returns A DVModelEvents instance - * @private - */ - let createEventsFrom: (events: Object) => DVModelEvents = (events: Object): DVModelEvents => { - let result = new DVModelEvents(); - if ((typeof events == "object") && (!Array.isArray(events))) { - Object.keys(events).forEach(k => { - setParams(result, k, events) - }); - } - return result; - } - - if (typeof json !== 'object') { - Log.e(TAG, "createDVModelFromJson: input is not JSON"); - return new DVModel("", "", "", createChildrenFrom([])); - } - - let jsonObject = json as Record; - return new DVModel( - jsonObject["compType"], - createAttributesFrom(jsonObject["attributes"]), - createEventsFrom(jsonObject["events"]), - createChildrenFrom(jsonObject["children"]), - jsonObject["build"] - ); -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterCallbackInformation.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterCallbackInformation.ets deleted file mode 100644 index 9afb0f1..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterCallbackInformation.ets +++ /dev/null @@ -1,60 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterCallbackInformation.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -import FlutterNapi from '../embedding/engine/FlutterNapi'; - -/** - * A class representing information for a callback registered using `PluginUtilities` from `dart:ui`. - * - * This class holds information about callbacks that can be invoked from native code. - */ -export class FlutterCallbackInformation { - /** The name of the callback function. */ - callbackName?: string; - /** The class name containing the callback. */ - callbackClassName?: string; - /** The library path where the callback is defined. */ - callbackLibraryPath?: string; - - /** - * Gets callback information for a given handle. - * - * @param handle - The handle for the callback, generated by `PluginUtilities.getCallbackHandle` in - * `dart:ui` - * @returns An instance of FlutterCallbackInformation for the provided handle, or null if not found - */ - static lookupCallbackInformation(handle: number): FlutterCallbackInformation | null { - return FlutterNapi.nativeLookupCallbackInformation(handle); - } - - /** - * Constructs a new FlutterCallbackInformation instance. - * @param callbackName - The name of the callback function - * @param callbackClassName - The class name containing the callback - * @param callbackLibraryPath - The library path where the callback is defined - */ - constructor(callbackName?: string, callbackClassName?: string, callbackLibraryPath?: string) { - this.callbackName = callbackName; - this.callbackClassName = callbackClassName; - this.callbackLibraryPath = callbackLibraryPath; - } - - /** - * Initializes the callback information. - * @param callbackName - The name of the callback function - * @param callbackClassName - The class name containing the callback - * @param callbackLibraryPath - The library path where the callback is defined - */ - init(callbackName: string, callbackClassName: string, callbackLibraryPath: string) { - this.callbackName = callbackName; - this.callbackClassName = callbackClassName; - this.callbackLibraryPath = callbackLibraryPath; - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterRunArguments.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterRunArguments.ets deleted file mode 100644 index 7a32ede..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterRunArguments.ets +++ /dev/null @@ -1,34 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on FlutterRunArguments.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ - -/** - * A class containing arguments for entering a FlutterNativeView's isolate for the first time. - * Contains the bundle path, entrypoint, and library path. - */ -export default class FlutterRunArguments { - /** The path to the Flutter bundle. */ - public bundlePath: string; - /** The entrypoint function name. */ - public entrypoint: string; - /** The path to the Dart library. */ - public libraryPath: string; - - /** - * Constructs a new FlutterRunArguments instance. - * @param bundlePath - The path to the Flutter bundle - * @param entrypoint - The entrypoint function name - * @param libraryPath - The path to the Dart library - */ - constructor(bundlePath: string, entrypoint: string, libraryPath: string) { - this.bundlePath = bundlePath; - this.entrypoint = entrypoint; - this.libraryPath = libraryPath; - } -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterView.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterView.ets deleted file mode 100644 index 3530e87..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterView.ets +++ /dev/null @@ -1,1176 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -*/ - -import FlutterEngine from '../embedding/engine/FlutterEngine'; -import Log from '../util/Log'; -import { DVModel, DVModelChildren, DVModelEvents, DVModelParameters } from './DynamicView/dynamicView'; -import { display } from '@kit.ArkUI' -import FlutterManager from '../embedding/ohos/FlutterManager'; -import window from '@ohos.window'; -import KeyboardManager from '../embedding/ohos/KeyboardManager'; -import MouseCursorPlugin from '../plugin/mouse/MouseCursorPlugin'; -import Settings from '../embedding/ohos/Settings'; -import ArrayList from '@ohos.util.ArrayList'; -import { EmbeddingNodeController } from '../embedding/ohos/EmbeddingNodeController'; -import PlatformView, { Params } from '../plugin/platform/PlatformView'; -import hiTraceMeter from '@ohos.hiTraceMeter' -import { JSON } from '@kit.ArkTS'; -import TextInputPlugin from '../plugin/editing/TextInputPlugin'; -import { accessibility } from '@kit.AccessibilityKit'; -import { uiObserver } from '@kit.ArkUI'; -import { common } from '@kit.AbilityKit'; -import { deviceInfo } from '@kit.BasicServicesKit'; -import KeyEventChannel from '../embedding/engine/systemchannels/KeyEventChannel'; -import StatusBarClickChannel from '../embedding/engine/systemchannels/StatusBarClickChannel'; -import { commonEventManager, BusinessError } from '@kit.BasicServicesKit'; - -const TAG = "FlutterViewTag"; -const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS'; -const DPI_SCALE_RESET: number = -1; - -/** - * Metrics describing the viewport dimensions and insets. - * This class holds information about the physical dimensions, padding, insets, and display features. - */ -export class ViewportMetrics { - /** Device pixel ratio for converting logical to physical pixels. */ - devicePixelRatio: number = 1.0; - /** Physical width of the viewport in pixels. */ - physicalWidth: number = 0; - /** Physical height of the viewport in pixels. */ - physicalHeight: number = 0; - /** Top padding of the viewport in physical pixels. */ - physicalViewPaddingTop: number = 0; - /** Right padding of the viewport in physical pixels. */ - physicalViewPaddingRight: number = 0; - /** Bottom padding of the viewport in physical pixels. */ - physicalViewPaddingBottom: number = 0; - /** Left padding of the viewport in physical pixels. */ - physicalViewPaddingLeft: number = 0; - /** Top inset of the viewport in physical pixels. */ - physicalViewInsetTop: number = 0; - /** Right inset of the viewport in physical pixels. */ - physicalViewInsetRight: number = 0; - /** Bottom inset of the viewport in physical pixels. */ - physicalViewInsetBottom: number = 0; - /** Left inset of the viewport in physical pixels. */ - physicalViewInsetLeft: number = 0; - /** Top system gesture inset in physical pixels. */ - systemGestureInsetTop: number = 0; - /** Right system gesture inset in physical pixels. */ - systemGestureInsetRight: number = 0; - /** Bottom system gesture inset in physical pixels. */ - systemGestureInsetBottom: number = 0; - /** Left system gesture inset in physical pixels. */ - systemGestureInsetLeft: number = 0; - /** Physical touch slop value, or -1 if not set. */ - physicalTouchSlop: number = -1; - /** List of display features such as folds, hinges, or cutouts. */ - displayFeatures: ArrayList = new ArrayList(); - - /** - * Creates a deep copy of this ViewportMetrics instance. - * @returns A new ViewportMetrics instance with copied values - */ - clone(): ViewportMetrics { - const copy = new ViewportMetrics(); - copy.devicePixelRatio = this.devicePixelRatio; - copy.physicalWidth = this.physicalWidth; - copy.physicalHeight = this.physicalHeight; - copy.physicalViewPaddingTop = this.physicalViewPaddingTop; - copy.physicalViewPaddingRight = this.physicalViewPaddingRight; - copy.physicalViewPaddingBottom = this.physicalViewPaddingBottom; - copy.physicalViewPaddingLeft = this.physicalViewPaddingLeft; - copy.physicalViewInsetTop = this.physicalViewInsetTop; - copy.physicalViewInsetRight = this.physicalViewInsetRight; - copy.physicalViewInsetBottom = this.physicalViewInsetBottom; - copy.physicalViewInsetLeft = this.physicalViewInsetLeft; - copy.systemGestureInsetTop = this.systemGestureInsetTop; - copy.systemGestureInsetRight = this.systemGestureInsetRight; - copy.systemGestureInsetBottom = this.systemGestureInsetBottom; - copy.systemGestureInsetLeft = this.systemGestureInsetLeft; - copy.physicalTouchSlop = this.physicalTouchSlop; - copy.displayFeatures = this.displayFeatures; - return copy; - } - - /** - * Checks if this ViewportMetrics is equal to another. - * @param other - The other ViewportMetrics to compare with - * @returns True if all metrics are equal, false otherwise - */ - isEqual(other: ViewportMetrics): boolean { - return this.devicePixelRatio === other.devicePixelRatio && - this.physicalWidth === other.physicalWidth && - this.physicalHeight === other.physicalHeight && - this.physicalViewPaddingTop === other.physicalViewPaddingTop && - this.physicalViewPaddingRight === other.physicalViewPaddingRight && - this.physicalViewPaddingBottom === other.physicalViewPaddingBottom && - this.physicalViewPaddingLeft === other.physicalViewPaddingLeft && - this.physicalViewInsetTop === other.physicalViewInsetTop && - this.physicalViewInsetRight === other.physicalViewInsetRight && - this.physicalViewInsetBottom === other.physicalViewInsetBottom && - this.physicalViewInsetLeft === other.physicalViewInsetLeft && - this.systemGestureInsetTop === other.systemGestureInsetTop && - this.systemGestureInsetRight === other.systemGestureInsetRight && - this.systemGestureInsetBottom === other.systemGestureInsetBottom && - this.systemGestureInsetLeft === other.systemGestureInsetLeft && - this.physicalTouchSlop === other.physicalTouchSlop && - this.displayFeatures === other.displayFeatures; - } -} - -/** - * Represents a display feature such as a fold, hinge, or cutout. - */ -export class DisplayFeature { - /** Bounding rectangle of the display feature. */ - bound: display.Rect; - /** Type of the display feature (fold, hinge, cutout, etc.). */ - type: DisplayFeatureType; - /** State of the display feature. */ - state: DisplayFeatureState; - - /** - * Constructs a new DisplayFeature instance. - * @param bound - The bounding rectangle of the feature - * @param type - The type of display feature - * @param state - The state of the display feature - */ - constructor(bound: display.Rect, type: DisplayFeatureType, state: DisplayFeatureState) { - this.bound = bound; - this.type = type; - this.state = state; - } - - /** - * Gets the bounding rectangle of the display feature. - * @returns The bounding rectangle - */ - getBound(): display.Rect { - return this.bound; - } - - /** - * Gets the type of the display feature. - * @returns The display feature type - */ - getType(): DisplayFeatureType { - return this.type; - } - - /** - * Gets the state of the display feature. - * @returns The display feature state - */ - getState(): DisplayFeatureState { - return this.state - } - - /** - * Sets the bounding rectangle of the display feature. - * @param bound - The bounding rectangle to set - */ - setBound(bound: display.Rect): void { - this.bound = bound; - } - - /** - * Sets the type of the display feature. - * @param type - The display feature type to set - */ - setType(type: DisplayFeatureType): void { - this.type = type; - } - - /** - * Sets the state of the display feature. - * @param state - The display feature state to set - */ - setState(state: DisplayFeatureState): void { - this.state = state; - } -} - -/** - * Enumeration of display feature types. - */ -export enum DisplayFeatureType { - /** Unknown display feature type */ - UNKNOWN = 0, - /** Fold display feature */ - FOLD = 1, - /** Hinge display feature */ - HINGE = 2, - /** Cutout display feature */ - CUTOUT = 3 -} - -/** - * Enumeration of display feature states. - */ -export enum DisplayFeatureState { - /** Unknown state */ - UNKNOWN = 0, - /** Flat posture */ - POSTURE_FLAT = 1, - /** Half-opened posture */ - POSTURE_HALF_OPENED = 2, -} - -/** - * Enumeration of display fold status. - */ -export enum DisplayFoldStatus { - /** Unknown fold status */ - FOLD_STATUS_UNKNOWN = 0, - /** Display is expanded */ - FOLD_STATUS_EXPANDED = 1, - /** Display is folded */ - FOLD_STATUS_FOLDED = 2, - /** Display is half-folded */ - FOLD_STATUS_HALF_FOLDED = 3 -} - -type callbackNumber = () => number - -/** - * Parameters for platform view layout. - * @deprecated since 3.7 - */ -export class PlatformViewParas { - /** The width of the platform view. */ - width: number = 0.0; - /** The height of the platform view. */ - height: number = 0.0; - /** The top position of the platform view. */ - top: number = 0.0; - /** The left position of the platform view. */ - left: number = 0.0; - /** The layout direction for the platform view. */ - direction: Direction = Direction.Auto; - - /** - * Sets the layout values. - * @param width - The width - * @param height - The height - * @param top - The top position - * @param left - The left position - */ - setValue(width: number, height: number, top: number, left: number): void { - this.width = width; - this.height = height; - this.top = top; - this.left = left; - } - - /** - * Sets the offset position. - * @param top - The top offset - * @param left - The left offset - */ - setOffset(top: number, left: number): void { - this.top = top; - this.left = left; - } -} - -/** - * Main view class for rendering Flutter content in OpenHarmony applications. - * This class manages the Flutter engine, viewport metrics, keyboard handling, - * and all interactions between Flutter and the native platform. - */ -export class FlutterView { - private flutterEngine: FlutterEngine | null = null - private id: string = "" - private isActive: boolean = true - private dVModel: DVModel = - new DVModel("Stack", new DVModelParameters(), new DVModelEvents(), new DVModelChildren(), null); - /** The wrapped builder for creating the view, or undefined if not set. */ - private wrapBuilder: WrappedBuilder<[Params]> | undefined = undefined; - private platformView: PlatformView | undefined = undefined; - private isSurfaceAvailableForRendering: boolean = false - private viewportMetrics = new ViewportMetrics(); - private displayInfo?: display.Display; - private keyboardManager: KeyboardManager | null = null; - private statusBarClickChannel: StatusBarClickChannel|null = null; - private mainWindow: window.Window | null = null; - private mouseCursorPlugin?: MouseCursorPlugin; - private textInputPlugin?: TextInputPlugin; - private uiContext?: UIContext | undefined; - private context: Context; - private settings?: Settings; - private mFirstFrameListeners: ArrayList; - private mFirstPreloadFrameListeners: ArrayList; - private isFlutterUiDisplayed: boolean = false; - private isFlutterUiPreload: boolean = false; - private surfaceId: string = "0"; - private nodeController: EmbeddingNodeController = new EmbeddingNodeController(); - private platformViewSize: PlatformViewParas = new PlatformViewParas(); - private checkFullScreen: boolean = true; - private checkKeyboard: boolean = true; - private checkGesture: boolean = true; - private checkAiBar: boolean = true; - private frameCache: boolean = true; - private paddingTop?: number; - private systemAvoidArea: window.AvoidArea; - private navigationAvoidArea: window.AvoidArea; - private gestureAvoidArea: window.AvoidArea; - private keyboardAvoidArea: window.AvoidArea; - private needSetViewport: boolean = false; - private windowPosition: window.Rect | null = null; - // 默认值5,参考:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-basic-gestures-pangesture - private callbackValue: callbackNumber = () => 5; - // DPI update flag to distinguish between system DPI updates and custom DPI updates - private isDevicePixelRatioAdaptive: boolean = false; - private lastHeight: number = 0; - private statusBarClickSubscriber: commonEventManager.CommonEventSubscriber | null = null; - - /** - * Constructs a new FlutterView instance. - * @param viewId - The unique identifier for this view - * @param context - The application context - */ - constructor(viewId: string, context: Context) { - this.id = viewId; - this.context = context; - this.displayInfo = display.getDefaultDisplaySync(); - this.viewportMetrics.devicePixelRatio = this.displayInfo?.densityPixels; - this.buildDisplayFeatures(display.getFoldStatus()); - - this.mainWindow = FlutterManager.getInstance() - .getWindowStage(FlutterManager.getInstance().getUIAbility(context)) - ?.getMainWindowSync(); - this.mFirstFrameListeners = new ArrayList(); - this.mFirstPreloadFrameListeners = new ArrayList(); - - this.mainWindow?.on('windowSizeChange', this.windowSizeChangeCallback); - this.mainWindow?.on('avoidAreaChange', this.avoidAreaChangeCallback); - this.mainWindow?.on('windowStatusChange', this.windowStatusChangeCallback); - this.mainWindow?.on('keyboardHeightChange', this.keyboardHeightChangeCallback); - this.mainWindow?.on('windowRectChange', this.windowRectChangeCallback); - //监听系统无障碍服务状态改变 - accessibility.on('accessibilityStateChange', this.accessibilityStateChangeCallback); - - this.systemAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); - this.navigationAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR); - this.gestureAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM_GESTURE); - this.keyboardAvoidArea = this.mainWindow?.getWindowAvoidArea(window.AvoidAreaType.TYPE_KEYBOARD); - commonEventManager.createSubscriber( - { events: ["usual.event.CLICK_STATUSBAR"] }, this.onStatusBarClick); - - // 监听折叠状态的改变 - display?.on('foldStatusChange', this.foldStatusChangeCallback); - - // 监听路由跳转(Flutter跳转鸿蒙原生),软键盘处理 - uiObserver.on('routerPageUpdate', context as common.UIAbilityContext, this.routerPageUpdateCallback); - - // Subscribes to display changes. Example: event that the display size is changed. - try { - display.on("change", this.displayChangeCallback); - } catch (e) { - Log.e(TAG, "displayInfo error" + JSON.stringify(e)); - } - this.context.eventHub.on('changeDevicePixelRatio', this.onDevicePixelRatioChange); - } - - onDevicePixelRatioChange = (dpiScaleFactor: number) => { - try { - // Get display information - this.displayInfo = display.getDefaultDisplaySync(); - - // Set DPI for text input channel - this.flutterEngine?.getTextInputChannel()?.setDevicePixelRatio(this.displayInfo.densityPixels); - - // Calculate device pixel ratio - // When dpiScaleFactor is DPI_SCALE_RESET, reset DPI to system default - let newDevicePixelRatio: number; - if (dpiScaleFactor === DPI_SCALE_RESET) { - newDevicePixelRatio = this.displayInfo.densityPixels; - Log.d(TAG, "Resetting device pixel ratio to system default: " + newDevicePixelRatio); - // When resetting DPI, set the flag to false - this.isDevicePixelRatioAdaptive = false; - } else { - newDevicePixelRatio = this.displayInfo.densityPixels * dpiScaleFactor; - Log.d(TAG, "Scaling device pixel ratio by factor " + dpiScaleFactor + ": " + newDevicePixelRatio); - // When customizing DPI, set the flag to true - this.isDevicePixelRatioAdaptive = true; - } - - // Only update when DPI actually changes - if (newDevicePixelRatio !== this.viewportMetrics.devicePixelRatio) { - this.viewportMetrics.devicePixelRatio = newDevicePixelRatio; - Log.i(TAG, "Device pixel ratio updated: " + newDevicePixelRatio + - " (scale factor: " + dpiScaleFactor + ", system DPI: " + this.displayInfo.densityPixels + ")"); - this.needSetViewport = true; - this.onAreaChange(null); - } - } catch (e) { - Log.e(TAG, "Error updating device pixel ratio: " + JSON.stringify(e)); - } - } - - /** - * Sets the callback for getting touch slop value. - * @param callback - The callback function that returns the touch slop value - */ - setTouchSlopCallbackValue(callback: callbackNumber) { - this.callbackValue = callback; - } - - private async buildDisplayFeatures(foldStatus: display.FoldStatus) { - let displayFeatures: ArrayList = new ArrayList(); - const displayInfo = display.getDefaultDisplaySync(); - /* - There are some bugs about getCurrentFoldCreaseRegion: - * 1. the crease region area is inaccurate - * 2. the creaseRegion.displayId and displayInfo.id are always both 0, in which case it is unable to - * distinguish whether the crease region is on the current screen. - * So do not add FOLD feature until the bugs are fixed. - */ - // if (display.isFoldable()) { - // let state: DisplayFeatureState = DisplayFeatureState.UNKNOWN; - // if (foldStatus == display.FoldStatus.FOLD_STATUS_EXPANDED) { - // state = DisplayFeatureState.POSTURE_FLAT; - // } else if (foldStatus == display.FoldStatus.FOLD_STATUS_HALF_FOLDED) { - // state = DisplayFeatureState.POSTURE_HALF_OPENED; - // } else { - // state = DisplayFeatureState.UNKNOWN; - // } - // let creaseRegion: display.FoldCreaseRegion = display.getCurrentFoldCreaseRegion(); - // if (creaseRegion.displayId == displayInfo.id) { - // for (let bound of creaseRegion.creaseRects) { - // displayFeatures.add(new DisplayFeature(bound, DisplayFeatureType.FOLD, state)); - // } - // } - // } - - let cutoutInfos = await displayInfo?.getCutoutInfo(); - for (let bound of cutoutInfos.boundingRects) { - displayFeatures.add(new DisplayFeature(bound, DisplayFeatureType.CUTOUT, DisplayFeatureState.UNKNOWN)); - } - Log.d(TAG, `device displayFeatures is : ${JSON.stringify(displayFeatures)}`); - this.viewportMetrics.displayFeatures = displayFeatures; - this.updateViewportMetrics(); - } - - private routerPageUpdateCallback = (info: uiObserver.RouterPageInfo) => { - if (this.getKeyboardHeight() !== 0 && info.state === uiObserver.RouterPageState.ON_PAGE_SHOW) { - this.flutterEngine?.getTextInputChannel()?.textInputMethodHandler?.hide(); - } - } - - private avoidAreaChangeCallback = (data: window.AvoidAreaOptions) => { - Log.i(TAG, "avoidAreaChangeCallback, type=" + data.type + ", area=" + JSON.stringify(data.area)); - - switch (data.type) { - case window.AvoidAreaType.TYPE_SYSTEM: - this.systemAvoidArea = data.area; - break; - case window.AvoidAreaType.TYPE_SYSTEM_GESTURE: - this.gestureAvoidArea = data.area; - break; - case window.AvoidAreaType.TYPE_KEYBOARD: - if (this.getKeyboardHeight() > 0) { - this.keyboardAvoidArea = data.area; - } else { - this.keyboardAvoidArea.bottomRect = { left: 0, top: 0, width: 0, height: 0 }; - } - break; - case window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR: - this.navigationAvoidArea = data.area; - break; - default: - break; - } - if (this.isAttachedToFlutterEngine()) { - this.onAreaChange(null); - } - } - private windowSizeChangeCallback = (data: window.Size) => { - Log.i(TAG, `windowSizeChangeCallback: width=${data.width}, height=${data.height}, lastHeight=${this.lastHeight}`); - - // Only handle when height changes - if (this.lastHeight !== data.height) { - try { - // If DPI has been customized, do not process system DPI changes - this.displayInfo = display.getDefaultDisplaySync(); - this.flutterEngine?.getTextInputChannel()?.setDevicePixelRatio(this.displayInfo.densityPixels); - - let devicePixelRatio: number = this.displayInfo.densityPixels; - Log.i(TAG, `windowSizeChangeCallback devicePixelRatio: ${devicePixelRatio}, viewportMetrics.devicePixelRatio: ${this.viewportMetrics.devicePixelRatio}`); - - if (devicePixelRatio !== this.viewportMetrics.devicePixelRatio) { - this.viewportMetrics.devicePixelRatio = devicePixelRatio; - Log.i(TAG, `windowSizeChangeCallback: Updated devicePixelRatio to ${devicePixelRatio}`); - this.needSetViewport = true; - } - } catch (e) { - Log.e(TAG, `windowSizeChangeCallback error: ${JSON.stringify(e)}`); - } - } - - // Update lastHeight - this.lastHeight = data.height; - - // Notify area change, this method handles all necessary view updates - if (this.isAttachedToFlutterEngine()) { - this.onAreaChange(null); - } - } - private windowStatusChangeCallback = (data: window.WindowStatusType) => { - Log.i(TAG, "windowStatusChangeCallback " + data); - if (this.isAttachedToFlutterEngine()) { - FlutterManager.getInstance().getFullScreenListener().onScreenStateChanged(data); - } - }; - private displayChangeCallback = (data: number) => { - // If DPI has been customized, do not process system DPI changes - if (this.isDevicePixelRatioAdaptive) { - Log.d(TAG, "Skipping display change callback due to custom DPI setting"); - return; - } - - this.displayInfo = display.getDefaultDisplaySync(); - this.flutterEngine?.getTextInputChannel()?.setDevicePixelRatio(this.displayInfo.densityPixels); - let devicePixelRatio: number = this.displayInfo?.densityPixels; - Log.i(TAG, "Display on: " + JSON.stringify(this.displayInfo) + ". Display id:" + JSON.stringify(data)) - if (devicePixelRatio != this.viewportMetrics.devicePixelRatio) { - this.viewportMetrics.devicePixelRatio = devicePixelRatio; - this.needSetViewport = true; - this.onAreaChange(null); - } - this.flutterEngine?.getFlutterNapi()?.updateRefreshRate(this.displayInfo?.refreshRate); - } - private keyboardHeightChangeCallback = (data: number) => { - Log.i(TAG, "keyboardHeightChangeCallback " + data); - if (this.keyboardAvoidArea) { - this.keyboardAvoidArea.bottomRect.height = data; - } - this.onAreaChange(null); - }; - private windowRectChangeCallback = (data: window.RectChangeOptions) => { - Log.i(TAG, "windowRectChangeCallback " + data); - this.windowPosition = data.rect as window.Rect; - this.flutterEngine?.getTextInputChannel()?.setWindowPosition(this.windowPosition); - } - private accessibilityStateChangeCallback = (data: boolean) => { - Log.i(TAG, `subscribe accessibility state change, result: ${JSON.stringify(data)}`); - this.flutterEngine?.getFlutterNapi()?.accessibilityStateChange(data); - } - private foldStatusChangeCallback = (data: display.FoldStatus) => { - Log.d(TAG, `Fold status change to ${JSON.stringify(data)}`) - this.buildDisplayFeatures(data); - } - - /** - * Gets the view ID. - * @returns The view ID - */ - getId(): string { - return this.id; - } - - /** - * Sets the active state of the view. - * @param value - True to activate, false to deactivate - */ - setActive(value: boolean): void { - this.isActive = value; - } - - /** - * Gets the active state of the view. - * @returns True if active, false otherwise - */ - getActive(): boolean { - return this.isActive; - } - - /** - * Sets the surface ID for rendering. - * @param surfaceId - The surface ID - */ - setSurfaceId(surfaceId: string): void { - this.surfaceId = surfaceId; - } - - /** - * Gets the surface ID. - * @returns The surface ID - */ - getSurfaceId(): string { - return this.surfaceId; - } - - /** - * Gets the embedding node controller. - * @deprecated since 3.7 - * @returns The EmbeddingNodeController instance - */ - getEmbeddingNodeController(): EmbeddingNodeController { - return this.nodeController; - } - - /** - * Sets the wrapped builder for platform views. - * @param wrappedBuilder - The WrappedBuilder instance - */ - setWrappedBuilder(wrappedBuilder: WrappedBuilder<[Params]>) { - this.wrapBuilder = wrappedBuilder; - } - - /** - * Gets the wrapped builder. - * @returns The WrappedBuilder instance, or undefined if not set - */ - getWrappedBuilder(): WrappedBuilder<[Params]> | undefined { - return this.wrapBuilder; - } - - /** - * Sets the platform view. - * @param platformView - The PlatformView instance - */ - setPlatformView(platformView: PlatformView) { - this.platformView = platformView; - } - - /** - * Gets the platform view. - * @returns The PlatformView instance, or undefined if not set - */ - getPlatformView(): PlatformView | undefined { - return this.platformView; - } - - /** - * Gets the platform view size. - * @deprecated since 3.7 - * @returns The PlatformViewParas instance - */ - getPlatformViewSize(): PlatformViewParas { - return this.platformViewSize; - } - - /** - * Gets the DynamicView model. - * @returns The DVModel instance - */ - getDVModel() { - return this.dVModel; - } - - /** - * Gets the current keyboard height. - * @returns The keyboard height in pixels, or 0 if keyboard is not visible - */ - getKeyboardHeight() { - return this.keyboardAvoidArea?.bottomRect.height - } - - /** - * Called when the view is being destroyed. - * Cleans up all event listeners and resources. - */ - onDestroy() { - try { - uiObserver.off('routerPageUpdate', this.context as common.UIAbilityContext, this.routerPageUpdateCallback); - this.mainWindow?.off('windowSizeChange', this.windowSizeChangeCallback); - this.mainWindow?.off('avoidAreaChange', this.avoidAreaChangeCallback); - this.mainWindow?.off('windowStatusChange', this.windowStatusChangeCallback); - this.mainWindow?.off('keyboardHeightChange', this.keyboardHeightChangeCallback); - this.mainWindow?.off('windowRectChange', this.windowRectChangeCallback); - accessibility.off('accessibilityStateChange', this.accessibilityStateChangeCallback); - display.off('foldStatusChange', this.foldStatusChangeCallback); - this.context.eventHub.off('changeDevicePixelRatio', this.onDevicePixelRatioChange); - } catch (e) { - Log.e(TAG, "mainWindow off error: " + JSON.stringify(e)); - } - this.mainWindow = null; - - try { - display.off("change", this.displayChangeCallback); - } catch (e) { - Log.e(TAG, "displayInfo off error" + JSON.stringify(e)); - } - FlutterManager.getInstance().deleteFlutterView(this.id, this); - - this.nodeController.disposeFrameNode(); - } - - /** - * Attaches this view to a Flutter engine. - * @param flutterEngine - The FlutterEngine to attach to - */ - attachToFlutterEngine(flutterEngine: FlutterEngine): void { - hiTraceMeter.startTrace("attachToFlutterEngine", 0); - if (this.isAttachedToFlutterEngine()) { - if (flutterEngine == this.flutterEngine) { - Log.i(TAG, "Already attached to this engine. Doing nothing."); - return; - } - // Detach from a previous FlutterEngine so we can attach to this new one.f - Log.i( - TAG, - "Currently attached to a different engine. Detaching and then attaching" - + " to new engine."); - this.detachFromFlutterEngine(); - } - Log.i(TAG, "attachToFlutterEngine"); - this.flutterEngine = flutterEngine; - this.flutterEngine?.getFlutterNapi().xComponentAttachFlutterEngine(this.id) - this.flutterEngine?.getFlutterNapi()?.updateRefreshRate(this.displayInfo!.refreshRate) - this.flutterEngine?.getFlutterNapi()?.updateSize(this.displayInfo!.width, this.displayInfo!.height) - this.flutterEngine?.getFlutterNapi()?.updateDensity(this.displayInfo!.densityPixels) - this.flutterEngine?.getFlutterNapi().enableFrameCache(this.frameCache); - if (accessibility.isOpenAccessibilitySync()) { - this.flutterEngine?.getFlutterNapi()?.accessibilityStateChange(true); - } - flutterEngine.getPlatformViewsController()?.attachToView(this); - - let newArea: Area | null = { - width: px2vp(this.displayInfo!.width), - height: px2vp(this.displayInfo!.height), - position: { x: 0, y: 0 }, - globalPosition: { x: 0, y: 0 } - }; - if (this.viewportMetrics.physicalWidth != 0 || this.viewportMetrics.physicalHeight != 0) { - newArea = null; - } - this.onAreaChange(newArea, true); - - this.context.eventHub.on(EVENT_BACK_PRESS, () => { - if (this?.getKeyboardHeight() == 0) { - this.flutterEngine?.getNavigationChannel()?.popRoute(); - } else { - this.flutterEngine?.getTextInputChannel()?.textInputMethodHandler?.hide(); - } - }); - - let windowId = this.mainWindow?.getWindowProperties()?.id ?? 0 - this.mouseCursorPlugin = new MouseCursorPlugin(windowId, this.flutterEngine?.getMouseCursorChannel()!); - this.textInputPlugin = new TextInputPlugin(this.flutterEngine?.getTextInputChannel()!, this.id, - new KeyEventChannel(this.flutterEngine.dartExecutor)); - this.statusBarClickChannel = new StatusBarClickChannel(flutterEngine.dartExecutor) - this.keyboardManager = new KeyboardManager(flutterEngine, this.textInputPlugin!); - this.settings = new Settings(this.flutterEngine.getSettingsChannel()!); - this.sendSettings(); - this.isFlutterUiDisplayed = this.flutterEngine.getFlutterNapi().isDisplayingFlutterUi; - this.isFlutterUiPreload = this.flutterEngine.getFlutterNapi().isPreloadedFlutterUi; - if (this.isFlutterUiPreload) { - this.onFirstFrame(1); - } - if (this.isFlutterUiDisplayed) { - this.onFirstFrame(); - } - if (this.isSurfaceAvailableForRendering) { - this.flutterEngine?.processPendingMessages(); - } - hiTraceMeter.finishTrace("attachToFlutterEngine", 0); - } - - /** - * Called before drawing a frame. - * @param width - The width of the drawing area, defaults to display width - * @param height - The height of the drawing area, defaults to display height - */ - preDraw(width: number = 0, height: number = 0): void { - if (this.isAttachedToFlutterEngine()) { - if (width == 0 || height == 0) { - width = this.displayInfo!.width; - height = this.displayInfo!.height; - } - this.flutterEngine?.getFlutterNapi().xComponentPreDraw(this.id, width, height); - } - } - - /** - * Detaches this view from the Flutter engine. - * Cleans up all engine-related resources. - */ - detachFromFlutterEngine(): void { - Log.i(TAG, "detachFromFlutterEngine"); - if (!this.isAttachedToFlutterEngine()) { - Log.d(TAG, "FlutterView not attached to an engine. Not detaching."); - return; - } - if (this.isSurfaceAvailableForRendering) { - this.flutterEngine!!.getFlutterNapi().xComponentDetachFlutterEngine(this.id) - } - this.flutterEngine?.getPlatformViewsController()?.detachFromView(); - this.flutterEngine = null; - this.keyboardManager = null; - this.textInputPlugin?.destroy(); - this.context?.eventHub.off(EVENT_BACK_PRESS); - } - - /** - * Called when the window is created. - * Initializes the UIContext and sends settings to Flutter. - */ - onWindowCreated() { - Log.d(TAG, "received onwindowCreated."); - let _UIContext = this.mainWindow?.getUIContext(); - this.uiContext = _UIContext; - this.sendSettings(); - Log.d(TAG, "uiContext init and sendSettings finished."); - } - - /** - * Sends system settings to Flutter. - */ - sendSettings(): void { - if (this.uiContext != undefined && this.isAttachedToFlutterEngine()) { - this.settings?.sendSettings(this.uiContext.getMediaQuery()); - } else { - Log.e(TAG, "UIContext is null, cannot send Settings!"); - } - } - - /** - * Called when the rendering surface is created. - * Marks the surface as available and processes pending messages. - */ - onSurfaceCreated() { - this.isSurfaceAvailableForRendering = true; - this.flutterEngine?.processPendingMessages(); - } - - /** - * Called when the rendering surface is destroyed. - * Marks the surface as unavailable and detaches from the engine. - */ - onSurfaceDestroyed() { - this.isSurfaceAvailableForRendering = false; - if (this.isAttachedToFlutterEngine()) { - this.flutterEngine!!.getFlutterNapi().xComponentDetachFlutterEngine(this.id) - } - } - - /** - * Called when the view area changes. - * Updates viewport metrics based on the new area and avoid areas. - * @param newArea - The new area, or null to use current display dimensions - * @param setFullScreen - Whether to set fullscreen mode - */ - onAreaChange(newArea: Area | null, setFullScreen: boolean = false) { - const originalMetrics = this.viewportMetrics.clone(); - if (newArea != null) { - this.viewportMetrics.physicalWidth = vp2px(newArea.width as number); - this.viewportMetrics.physicalHeight = vp2px(newArea.height as number); - } - let fullScreen = false - // 根据是否全屏显示,设置标题栏高度 - if (this.checkFullScreen && - (setFullScreen || FlutterManager.getInstance().getFullScreenListener().useFullScreen())) { // 全屏显示 - fullScreen = true - if (this.paddingTop != undefined) { - this.viewportMetrics.physicalViewPaddingTop = this.paddingTop; - } else { - this.viewportMetrics.physicalViewPaddingTop = - this.systemAvoidArea?.topRect.height ?? this.viewportMetrics.physicalViewPaddingTop; - } - this.viewportMetrics.physicalViewPaddingBottom = - this.systemAvoidArea?.bottomRect.height ?? this.viewportMetrics.physicalViewPaddingBottom; - } else { // 非全屏显示 - this.viewportMetrics.physicalViewPaddingTop = this.paddingTop ?? 0; - this.viewportMetrics.physicalViewPaddingBottom = 0; - } - - this.viewportMetrics.physicalViewPaddingLeft = - this.systemAvoidArea?.leftRect.width ?? this.viewportMetrics.physicalViewPaddingLeft; - this.viewportMetrics.physicalViewPaddingRight = - this.systemAvoidArea?.rightRect.width ?? this.viewportMetrics.physicalViewPaddingRight; - - this.onKeyboardAreaChange(fullScreen) - this.onAiBarAreaChange(fullScreen) - this.onGestureAreaChange(fullScreen) - if (!this.viewportMetrics.isEqual(originalMetrics) || this.needSetViewport) { - if (!this.updateViewportMetrics()) { - this.needSetViewport = true; - } else { - this.needSetViewport = false; - } - } - } - - private onAiBarAreaChange(fullScreen: boolean = false) { - if (this.checkAiBar && this.navigationAvoidArea != null && fullScreen) { - this.viewportMetrics.physicalViewPaddingBottom = - Math.max(this.navigationAvoidArea?.bottomRect.height, this.viewportMetrics.physicalViewPaddingBottom) - } - } - - private onKeyboardAreaChange(fullScreen: boolean = false) { - if (this.checkKeyboard && fullScreen) { - this.viewportMetrics.physicalViewInsetTop = - this.keyboardAvoidArea?.topRect.height ?? this.viewportMetrics.physicalViewInsetTop - this.viewportMetrics.physicalViewInsetLeft = - this.keyboardAvoidArea?.leftRect.width ?? this.viewportMetrics.physicalViewInsetLeft - this.viewportMetrics.physicalViewInsetBottom = - this.keyboardAvoidArea?.bottomRect.height ?? this.viewportMetrics.physicalViewInsetBottom - this.viewportMetrics.physicalViewInsetRight = - this.keyboardAvoidArea?.rightRect.width ?? this.viewportMetrics.physicalViewInsetRight - } else { - this.viewportMetrics.physicalViewInsetTop = 0 - this.viewportMetrics.physicalViewInsetLeft = 0 - this.viewportMetrics.physicalViewInsetBottom = 0 - this.viewportMetrics.physicalViewInsetRight = 0 - } - } - - private onGestureAreaChange(fullScreen: boolean = false) { - if (this.checkGesture && fullScreen) { - this.viewportMetrics.systemGestureInsetTop = - this.gestureAvoidArea?.topRect.height ?? this.viewportMetrics.systemGestureInsetTop - this.viewportMetrics.systemGestureInsetLeft = - this.gestureAvoidArea?.leftRect.width ?? this.viewportMetrics.systemGestureInsetLeft - this.viewportMetrics.systemGestureInsetBottom = - Math.max(this.navigationAvoidArea?.bottomRect.height, this.gestureAvoidArea?.bottomRect.height) - this.viewportMetrics.systemGestureInsetRight = - this.gestureAvoidArea?.rightRect.width ?? this.viewportMetrics.systemGestureInsetRight - } else { - this.viewportMetrics.systemGestureInsetTop = 0 - this.viewportMetrics.systemGestureInsetLeft = 0 - this.viewportMetrics.systemGestureInsetBottom = 0 - this.viewportMetrics.systemGestureInsetRight = 0 - } - } - - /** - * Checks if this view is attached to a Flutter engine. - * @returns True if attached, false otherwise - */ - public isAttachedToFlutterEngine(): boolean { - return this.flutterEngine != null - } - - /** - * Checks if this view is attached to an engine with the specified shell holder ID. - * @param id - The shell holder ID to check - * @returns True if attached to an engine with the specified ID, false otherwise - */ - public isSameEngineShellHolderId(id: number): boolean { - if (this.flutterEngine) { - let flutterNapi = this.flutterEngine.getFlutterNapi(); - if (flutterNapi.nativeShellHolderId == id && id != 0) { - return true; - } - } - return false; - } - - private updateViewportMetrics(): boolean { - if (this.isAttachedToFlutterEngine()) { - const displayFeatures = this.viewportMetrics.displayFeatures; - let displayFeatureBound: number[] = new Array(displayFeatures.length * 4); - let displayFeatureType: number[] = new Array(displayFeatures.length); - let displayFeatureStatus: number[] = new Array(displayFeatures.length); - for (let i = 0; i < displayFeatures.length; i++) { - let singleFeatureBound = displayFeatures[i].getBound(); - displayFeatureBound[4 * i] = singleFeatureBound.left; - displayFeatureBound[4 * i + 1] = singleFeatureBound.top - displayFeatureBound[4 * i + 2] = singleFeatureBound.left + singleFeatureBound.width; - displayFeatureBound[4 * i + 3] = singleFeatureBound.top + singleFeatureBound.height; - displayFeatureType[i] = displayFeatures[i].getType(); - displayFeatureStatus[i] = displayFeatures[i].getState(); - } - - this.viewportMetrics.physicalTouchSlop = this.callbackValue(); - this?.flutterEngine?.getFlutterNapi()?.setViewportMetrics(this.viewportMetrics.devicePixelRatio, - this.viewportMetrics.physicalWidth, - this.viewportMetrics.physicalHeight, - this.viewportMetrics.physicalViewPaddingTop, - this.viewportMetrics.physicalViewPaddingRight, - this.viewportMetrics.physicalViewPaddingBottom, - this.viewportMetrics.physicalViewPaddingLeft, - this.viewportMetrics.physicalViewInsetTop, - this.viewportMetrics.physicalViewInsetRight, - this.viewportMetrics.physicalViewInsetBottom, - this.viewportMetrics.physicalViewInsetLeft, - this.viewportMetrics.systemGestureInsetTop, - this.viewportMetrics.systemGestureInsetRight, - this.viewportMetrics.systemGestureInsetBottom, - this.viewportMetrics.systemGestureInsetLeft, - this.viewportMetrics.physicalTouchSlop, - displayFeatureBound, - displayFeatureType, - displayFeatureStatus) - return true - } - return false - } - - onStatusBarClick = (err: BusinessError, subscriber: commonEventManager.CommonEventSubscriber) => { - this.statusBarClickSubscriber = subscriber - if(subscriber !== null) { - commonEventManager.subscribe(subscriber, (err, data) => { - this.statusBarClickChannel?.sendClick() - }) - } - } - - /** - * Handles key events before they reach the input method editor. - * @param event - The key event - * @returns True if the event was handled, false otherwise - */ - onKeyPreIme(event: KeyEvent): boolean { - return this.keyboardManager?.onKeyPreIme(event) ?? false; - } - - /** - * Handles key events. - * @param event - The key event - * @returns True if the event was handled, false otherwise - */ - onKeyEvent(event: KeyEvent): boolean { - return this.keyboardManager?.onKeyEvent(event) ?? false; - } - - /** - * Handles mouse wheel events. - * @param eventType - The event type - * @param event - The pan gesture event - */ - onMouseWheel(eventType: string, event: PanGestureEvent) { - if (deviceInfo.sdkApiVersion < 15) { // API15 及以后通过轴事件处理滚动 - this.flutterEngine?.getFlutterNapi()?.xComponentDisPatchMouseWheel(this.id, eventType, event); - } - } - - /** - * Adds a listener for the first frame event. - * @param listener - The listener to add - */ - addFirstFrameListener(listener: FirstFrameListener) { - this.mFirstFrameListeners.add(listener); - } - - /** - * Removes a first frame listener. - * @param listener - The listener to remove - */ - removeFirstFrameListener(listener: FirstFrameListener) { - this.mFirstFrameListeners.remove(listener); - } - - /** - * Adds a listener for the first preload frame event. - * @param listener - The listener to add - */ - addFirstPreloadFrameListener(listener: FirstPreloadFrameListener) { - this.mFirstPreloadFrameListeners.add(listener); - } - - /** - * Removes a first preload frame listener. - * @param listener - The listener to remove - */ - removeFirstPreloadFrameListener(listener: FirstPreloadFrameListener) { - this.mFirstPreloadFrameListeners.remove(listener); - } - - /** - * Checks if the first frame has been rendered. - * @returns True if the first frame has been rendered, false otherwise - */ - hasRenderedFirstFrame(): boolean { - return this.isFlutterUiDisplayed; - } - - /** - * Called when the first frame is rendered. - * Notifies all registered listeners. - * @param isPreload - 1 for preload frame, 0 for normal first frame - */ - onFirstFrame(isPreload: number = 0) { - if (isPreload) { - let listeners = this.mFirstPreloadFrameListeners.clone(); - listeners.forEach((listener) => { - listener.onFirstPreloadFrame(); - }) - } else { - let listeners = this.mFirstFrameListeners.clone(); - listeners.forEach((listener) => { - listener.onFirstFrame(); - }) - } - } - - /** - * Sets whether to check fullscreen mode. - * @param check - True to check fullscreen, false otherwise - */ - setCheckFullScreen(check: boolean) { - this.checkFullScreen = check; - } - - /** - * Sets whether to check keyboard area. - * @param check - True to check keyboard area, false otherwise - */ - setCheckKeyboard(check: boolean) { - this.checkKeyboard = check - } - - /** - * Sets whether to check gesture area. - * @param check - True to check gesture area, false otherwise - */ - setCheckGesture(check: boolean) { - this.checkGesture = check - } - - /** - * Sets whether to check AI bar area. - * @param check - True to check AI bar area, false otherwise - */ - setCheckAiBar(check: boolean) { - this.checkAiBar = check - } - - /** - * Sets the top padding value. - * @param paddingTop - The top padding value, or undefined to use default - */ - setPaddingTop(paddingTop?: number) { - this.paddingTop = paddingTop; - this.onAreaChange(null); - } - - /** - * Enables or disables frame caching. - * @param cacheEnable - True to enable frame cache, false to disable - */ - enableFrameCache(cacheEnable: boolean) { - this.frameCache = cacheEnable; - if (this.isAttachedToFlutterEngine()) { - this.flutterEngine?.getFlutterNapi().enableFrameCache(cacheEnable); - } - } -} - -/** - * Listener interface for first frame events. - */ -export interface FirstFrameListener { - /** - * Called when the first frame is rendered. - */ - onFirstFrame(): void; -} - -/** - * Listener interface for first preload frame events. - */ -export interface FirstPreloadFrameListener { - /** - * Called when the first preload frame is rendered. - */ - onFirstPreloadFrame(): void; -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/TextureRegistry.ets b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/TextureRegistry.ets deleted file mode 100644 index 6a7382d..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/TextureRegistry.ets +++ /dev/null @@ -1,92 +0,0 @@ -/* -* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. All rights reserved. -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE_KHZG file. -* -* Based on TextureRegistry.java originally written by -* Copyright (C) 2013 The Flutter Authors. -* -*/ -import image from '@ohos.multimedia.image'; - -/** - * Registry of backend textures used with a single FlutterView instance. - * Entries may be embedded into the Flutter view using the Texture widget. - * Textures can be created from surface textures, image receivers, or pixel maps. - */ -export interface TextureRegistry { - - createSurfaceTexture(): SurfaceTextureEntry; - - getTextureId(): number; - - registerTexture(textureId: number): SurfaceTextureEntry; - - registerSurfaceTexture(receiver: image.ImageReceiver): SurfaceTextureEntry; - - registerPixelMap(pixelMap: PixelMap): number; - - setTextureBackGroundPixelMap(textureId: number, pixelMap: PixelMap): void; - - /** - * @deprecated since 3.7 - */ - setTextureBackGroundColor(textureId: number, color: number): void; - - setTextureBufferSize(textureId: number, width: number, height: number): void; - - notifyTextureResizing(textureId: number, width: number, height: number): void; - - /** - * @deprecated since 3.22 - * @useinstead TextureRegistry#setExternalNativeImagePtr - */ - setExternalNativeImage(textureId: number, native_image: number): boolean; - - setExternalNativeImagePtr(textureId: number, native_image: bigint): boolean; - - resetExternalTexture(textureId: number, need_surfaceId: boolean): number; - - unregisterTexture(textureId: number): void; - - onTrimMemory(level: number): void; -} - -/** - * Entry representing a surface texture registered with the texture registry. - */ -export interface SurfaceTextureEntry { - getTextureId(): number; - - getSurfaceId(): number; - - /* - * This return value is OHNativeWindow* in native code. - * Once converted to OHNativeWindow*, it can be used to create an EGLSurface or VkSurface for rendering. - * This OHNativeWindow* needn't be released when invoking unregisterTexture. - */ - getNativeWindowId(): number; - - release(): void; -} - -/** - * Listener for frame consumption events. - */ -export interface OnFrameConsumedListener { - /** - * Called when a frame has been consumed. - */ - onFrameConsumed(): void; -} - -/** - * Listener for memory trim events. - */ -export interface OnTrimMemoryListener { - /** - * Called when memory should be trimmed. - * @param level - The memory trim level - */ - onTrimMemory(level: number): void; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/module.json b/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/module.json deleted file mode 100644 index f56b879..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/module.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "app": { - "bundleName": "com.example.config", - "debug": true, - "versionCode": 1000000, - "versionName": "1.0.0", - "minAPIVersion": 50000012, - "targetAPIVersion": 60001021, - "apiReleaseType": "Release", - "targetMinorAPIVersion": 0, - "targetPatchAPIVersion": 0, - "compileSdkVersion": "6.0.1.112", - "compileSdkType": "HarmonyOS", - "appEnvironments": [], - "bundleType": "app", - "buildMode": "debug" - }, - "module": { - "name": "flutter", - "type": "har", - "deviceTypes": [ - "default" - ], - "packageName": "@ohos/flutter_ohos", - "installationFree": false, - "virtualMachine": "ark", - "compileMode": "esmodule", - "dependencies": [] - } -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/BuildProfile.ets b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/BuildProfile.ets deleted file mode 100644 index c889882..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/BuildProfile.ets +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Use these variables when you tailor your ArkTS code. They must be of the const type. - */ -export const HAR_VERSION = '1.0.0-389fc59b68'; -export const BUILD_MODE_NAME = 'debug'; -export const DEBUG = true; -export const TARGET_NAME = 'default'; - -/** - * BuildProfile Class is used only for compatibility purposes. - */ -export default class BuildProfile { - static readonly HAR_VERSION = HAR_VERSION; - static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; - static readonly DEBUG = DEBUG; - static readonly TARGET_NAME = TARGET_NAME; -} \ No newline at end of file diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/build-profile.json5 b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/build-profile.json5 deleted file mode 100644 index e3fb899..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/build-profile.json5 +++ /dev/null @@ -1,34 +0,0 @@ -{ - "apiType": "stageMode", - "buildOption": { - "nativeLib": { - "debugSymbol": { - "strip": false, - "exclude": [] - } - } - }, - "buildOptionSet": [ - { - "name": "release", - "arkOptions": { - "obfuscation": { - "ruleOptions": { - "enable": false, - "files": [ - "./obfuscation-rules.txt" - ] - }, - "consumerFiles": [ - "./consumer-rules.txt" - ] - } - }, - }, - ], - "targets": [ - { - "name": "default" - } - ] -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/consumer-rules.txt b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/consumer-rules.txt deleted file mode 100644 index e69de29..0000000 diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/hvigorfile.ts b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/hvigorfile.ts deleted file mode 100644 index 4218707..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/hvigorfile.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { harTasks } from '@ohos/hvigor-ohos-plugin'; - -export default { - system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ - plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ -} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/libs/arm64-v8a/libflutter.so b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/libs/arm64-v8a/libflutter.so deleted file mode 100644 index c660a21..0000000 Binary files a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/libs/arm64-v8a/libflutter.so and /dev/null differ diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/obfuscation-rules.txt b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/obfuscation-rules.txt deleted file mode 100644 index 272efb6..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/obfuscation-rules.txt +++ /dev/null @@ -1,23 +0,0 @@ -# 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/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/oh-package.json5 b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/oh-package.json5 deleted file mode 100644 index 32ca322..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/oh-package.json5 +++ /dev/null @@ -1 +0,0 @@ -{"name":"flutter_native_arm64_v8a","version":"1.0.0-389fc59b68","description":"Place so files for flutter on ohos.","main":"Index.ets","author":"","license":"Apache-2.0","dependencies":{},"metadata":{"sourceRoots":["./src/main"],"debug":true,"nativeDebugSymbol":false},"compatibleSdkVersionStage":"beta1","compatibleSdkVersion":12,"compatibleSdkType":"HarmonyOS","obfuscated":false} diff --git a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/src/main/module.json b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/src/main/module.json deleted file mode 100644 index 87f6295..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/src/main/module.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "app": { - "bundleName": "com.example.config", - "debug": true, - "versionCode": 1000000, - "versionName": "1.0.0", - "minAPIVersion": 50000012, - "targetAPIVersion": 60001021, - "apiReleaseType": "Release", - "targetMinorAPIVersion": 0, - "targetPatchAPIVersion": 0, - "compileSdkVersion": "6.0.1.112", - "compileSdkType": "HarmonyOS", - "appEnvironments": [], - "bundleType": "app", - "buildMode": "debug" - }, - "module": { - "name": "flutter_native", - "type": "har", - "deviceTypes": [ - "default" - ], - "packageName": "flutter_native_arm64_v8a", - "installationFree": false, - "virtualMachine": "ark", - "compileMode": "esmodule", - "dependencies": [] - } -} diff --git a/pubspec.yaml b/pubspec.yaml index e43f8d7..56c2c90 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.61.0+61 +version: 0.69.0+69 environment: sdk: ^3.9.2 @@ -155,9 +155,8 @@ flutter: # Enable generation of localized strings generate: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/photos/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images diff --git a/scripts/debug_timestamp.dart b/scripts/debug_timestamp.dart new file mode 100644 index 0000000..6893e0b --- /dev/null +++ b/scripts/debug_timestamp.dart @@ -0,0 +1,32 @@ +// 时间戳调试脚本 +void main() { + final timestamp = 146085838541; + + print('🔍 时间戳调试'); + print('原始值: $timestamp (${timestamp.toString().length}位)'); + + // 尝试不同的解析方式 + print('\n方式1: 当作毫秒 (直接使用)'); + final d1 = DateTime.fromMillisecondsSinceEpoch(timestamp); + print('结果: ${d1.year}-${d1.month}-${d1.day} ${d1.hour}:${d1.minute}'); + + print('\n方式2: 当作秒 (×1000转毫秒)'); + final d2 = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); + print('结果: ${d2.year}-${d2.month}-${d2.day} ${d2.hour}:${d2.minute}'); + + print('\n方式3: 除以1000当作秒 (可能是微秒?)'); + final d3 = DateTime.fromMillisecondsSinceEpoch((timestamp / 1000).round() * 1000); + print('结果: ${d3.year}-${d3.month}-${d3.day} ${d3.hour}:${d3.minute}'); + + // 2016年的正确时间戳 + print('\n--- 参考值 ---'); + final date2016 = DateTime(2016, 4, 18, 15, 35); + print('2016-04-18 15:35 的毫秒时间戳: ${date2016.millisecondsSinceEpoch}'); + print('2016-04-18 15:35 的秒时间戳: ${date2016.millisecondsSinceEpoch ~/ 1000}'); + + // 检查是否差1000倍 + final correctMs = 1460858385000; // 13位 + print('\n如果是13位: $correctMs'); + final d4 = DateTime.fromMillisecondsSinceEpoch(correctMs); + print('结果: ${d4.year}-${d4.month}-${d4.day} ${d4.hour}:${d4.minute}'); +} diff --git a/scripts/test_pic_id_api.dart b/scripts/test_pic_id_api.dart new file mode 100644 index 0000000..af62c92 --- /dev/null +++ b/scripts/test_pic_id_api.dart @@ -0,0 +1,173 @@ +// 2026-04-11 | test_pic_id_api | 测试API连通性+完整Fallback链 +// 用法: dart scripts/test_pic_id_api.dart + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +const String _baseUrl = 'http://eat.wktyl.com/api'; +const String _picBase = 'http://eat.wktyl.com/api/assets/pic'; + +void main() async { + print('API 连通性 + 完整图片Fallback链测试\n'); + + await testApiConnectivity(); + await testPicIdField(); + await testFullFallbackChain(); + await testMultipleRecipes(); + + print('✅ 全部测试完成'); +} + +Future testApiConnectivity() async { + print('📡 [1/4] API 基础连通性'); + + final endpoints = [ + ('api.php?act=list&page=1&limit=3', '菜谱列表'), + ('api.php?act=categories&type=recipe', '分类列表'), + ('api.php?act=tags&limit=5', '标签列表'), + ('assets/back.png', '默认图back.png'), + ]; + + for (final (path, name) in endpoints) { + try { + final sw = Stopwatch()..start(); + final url = path.startsWith('assets') + ? '$_baseUrl/$path' + : '$_baseUrl/$path'; + final result = await httpGet(url); + sw.stop(); + + if (path.startsWith('assets')) { + print(' ✅ $name (${sw.elapsedMilliseconds}ms, ${result.length}B)'); + } else if (result['code'] == 200) { + print(' ✅ $name (${sw.elapsedMilliseconds}ms)'); + } else { + print(' ❌ $name → code=${result['code']}'); + } + } catch (e) { + print(' ❌ $name → $e'.substring(0, 50)); + } + } +} + +Future testPicIdField() async { + print('[2/4] pic_id 字段解析'); + + try { + final result = await httpGet('$_baseUrl/api.php?act=full&id=32891'); + final data = result['data']; + if (data == null) { + print('❌ 无数据'); + return; + } + + print('菜谱: ${data['title']} (#${data['id']})'); + print('pic_id: ${data['pic_id']}, recipe_id: ${data['id']}'); + + if (data['pic_id'] != null) { + print('✅ pic_id 有效'); + } else { + print('⚠️ pic_id 为null,回退使用 recipe_id'); + } + } catch (e) { + print('❌ 异常: $e'); + } +} + +Future testFullFallbackChain() async { + print('[3/4] 完整Fallback链可达性测试 (pic_id=7640)'); + + const int picId = 7640; + + final chain = [ + ('① coverUrl', '', false), + ('② {picId}a.jpg', '$_picBase/${picId}a.jpg', true), + ('③ {picId}b.jpg', '$_picBase/${picId}b.jpg', true), + ('④ {picId}.jpg', '$_picBase/$picId.jpg', true), + ('⑤ back.png', '$_baseUrl/assets/back.png', true), + ('⑥ 本地error.png', 'assets/data/photos/error.png', false), + ('⑦ 空白占位', '', false), + ]; + + for (final (label, url, isNetwork) in chain) { + if (url.isEmpty) { + if (label.contains('本地')) { + final file = File(url); + if (file.existsSync()) { + print('✅ $label → 文件存在'); + } else { + print('❌ $label → 文件不存在'); + } + } + continue; + } + + try { + final sw = Stopwatch()..start(); + + Uint8List? bytes; + int statusCode; + + if (isNetwork) { + final client = HttpClient(); + client.connectionTimeout = const Duration(seconds: 6); + final req = await client.getUrl(Uri.parse(url)); + final resp = await req.close(); + statusCode = resp.statusCode; + final bb = await resp.fold( + BytesBuilder(), + (b, d) => b..add(d), + ); + bytes = bb.toBytes(); + client.close(); + } else { + bytes = File(url).readAsBytesSync(); + statusCode = 200; + } + + sw.stop(); + + final ok = isNetwork + ? (statusCode == 200 && (bytes?.length ?? 0) > 100) + : (bytes != null && bytes!.length > 0); + + if (ok) { + final sizeKB = ((bytes!.length) / 1024).toStringAsFixed(1); + print('✅ $label → ${sw.elapsedMilliseconds}ms ($sizeKB KB)'); + } else { + print('⚠️ $label → HTTP $statusCode (${bytes?.length ?? 0}B)'); + } + } catch (e) { + print('❌ $label → 异常'); + } + } +} + +Future testMultipleRecipes() async { + print('[4/4] 多菜谱 pic_id 对比'); + + for (final id in [32891, 32892, 32893]) { + try { + final r = await httpGet('$_baseUrl/api.php?act=full&id=$id'); + final d = r['data']; + if (d == null) continue; + + final rid = d['id']; + final pid = d['pic_id']; + final diff = (rid == pid) ? '⚠️相同' : '✅不同'; + + print('#$rid pic_id=$pid $diff'); + } catch (_) {} + } +} + +Future> httpGet(String url) async { + final client = HttpClient(); + client.connectionTimeout = const Duration(seconds: 10); + final request = await client.getUrl(Uri.parse(url)); + final response = await request.close(); + final body = await response.transform(utf8.decoder).join(); + client.close(); + return json.decode(body) as Map; +}