diff --git a/CHANGELOG.md b/CHANGELOG.md index 7052418..a737e71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,226 @@ All notable changes to this project will be documented in this file. +## [0.99.24] - 2026-04-21 + +### ✨ 新功能 — 意见反馈系统重构:5轮对话+双发邮件+本地存储 + +#### 功能概述 +将原有的简单聊天式意见反馈页面重构为完整的**状态机驱动反馈系统**,支持5轮结构化对话、邮件双发(管理员+用户自动回复)、设备信息收集、本地持久化存储和防滥用限制。 + +#### 核心特性 +- 🎯 **状态机架构**:采用有限状态机管理反馈流程(idle → selecting → chatting_1~4 → completed → sending → success/failed) +- 💬 **5轮完整对话**:选择反馈类型(1轮) + 用户输入4条详细描述 = 共5轮交互 +- 📧 **双发邮件机制**: + - 第一封:发送到管理员(ad@avefs.com),包含完整对话记录+设备信息(UUID/IP/版本号)+系统日志 + - 第二封:如果用户填写了邮箱,自动发送iOS风格确认邮件(含反馈编号、处理流程、联系方式) +- 📱 **设备信息自动收集**:UUID、IP地址、应用版本、平台信息、操作系统版本、运行日志 +- 💾 **本地持久化存储**:使用shared_preferences保存所有反馈会话历史(最多50条) +- 🚫 **防滥用机制**: + - 每日限制3次提交 + - 冷却时间30分钟 + - 跨天自动重置计数器 +- 🎨 **iOS 26风格UI**: + - 对话进度指示器(第X/4轮,剩余X轮) + - 完成后显示提交面板(摘要卡片+邮箱输入+发送按钮) + - 发送中状态指示器 + - 成功/失败对话框(支持返回修改或重试或提交新反馈) + +#### 新增文件 +- `lib/src/models/feedback_model.dart` — 数据模型定义 + - `FeedbackState` 枚举(9种状态) + - `FeedbackSessionStatus` 枚举(draft/sent/failed) + - `DeviceInfo` 模型(uuid/ip/version/platform/logs) + - `FeedbackMessage` 模型(text/isUser/type/timestamp) + - `FeedbackSession` 模型(完整会话,支持不可变操作) +- `lib/src/services/local/feedback_storage_service.dart` — 本地存储服务 + - CRUD操作(save/getAll/get/delete/clearAll) + - 限制检查(canSubmitFeedback/recordSubmission/resetDailyCount) + - 自动跨天重置逻辑 + - 最大50条历史记录限制 + +#### 修改文件 +- `lib/src/pages/profile/social/chat_page.dart` — 完全重构为状态机驱动的反馈页面 + - 状态转换逻辑(_advanceToNextRound/_completeConversation) + - 设备信息收集(_createSession) + - 邮件发送流程(_submitFeedback + 成功/失败对话框) + - 动态底部UI切换(类型选择→输入栏→提交面板→发送中指示器) + - 进度显示(当前轮次/剩余轮次) + - 错误处理与重试机制 +- `lib/src/services/data/business/email_service.dart` — 扩展邮件服务 + - 新增 `sendFeedbackEmails()` 方法(双发入口) + - 新增 `_sendFeedbackToAdmin()` (发送给管理员,iOS风格HTML模板) + - 新增 `_sendAutoReplyToUser()` (自动回复给用户,绿色成功主题HTML) + - 新增 `_buildFeedbackAdminHtml()` (管理员邮件模板:用户信息卡片+对话记录+系统日志) + - 新增 `_buildFeedbackAutoReplyHtml()` (用户回复模板:感谢语+摘要+后续步骤+联系方式) + - 新增 `_getBaseEmailStyles()` / `_buildInfoRow()` / `_escapeHtml()` 辅助方法 + - 新增 `FeedbackSendResult` 结果类(success/adminSent/userSent/errorMessage) + +#### 技术亮点 +- **不可变数据设计**:FeedbackSession使用不可变操作(addMessage/markAsCompleted等),每次返回新实例,便于状态追踪和调试 +- **优雅降级**:设备信息获取失败不影响主流程(try-catch包裹每个采集点) +- **用户体验优化**: + - Bot引导语提示剩余轮次数 + - 邮箱选填并明确提示用途 + - 发送失败可返回修改或直接重试 + - 成功后可选择返回或立即开始新反馈 +- **邮件模板**: + - 采用Apple SF Pro字体栈 + - 响应式设计(移动端适配@media查询) + - 渐变色头部(紫色主题/绿色成功主题) + - 信息网格布局(2列自适应) + - 对话消息区分样式(用户/Bot不同背景色) + - 系统日志深色代码块样式 + +#### 使用示例 +```dart +// 导航到反馈页面 +Navigator.push(context, CupertinoPageRoute(builder: (_) => FeedbackPage())); + +// 用户流程: +// 1. 选择类型(Bug/功能建议/体验优化/其他) +// 2. 输入4条详细描述(带轮次提示) +// 3. 查看摘要,可选填邮箱 +// 4. 点击"📤 发送反馈" +// 5. 收到成功提示(如填了邮箱还会收到确认邮件) +``` + +#### 后续扩展方向 +- 🔮 反馈历史列表页面(查看已提交的反馈) +- 🔮 管理后台集成(API对接替代SMTP直发) +- 🔮 图片/截图附件支持 +- 🔮 反馈分类统计图表 +- 🔮 用户回复追踪(多轮客服对话) + +--- + +## [0.99.23] - 2026-04-21 + +### 🎨 UI优化 — 发送菜谱对话框线路选择样式重构 + +#### 修改内容 +- 🎨 **线路1(官方线路1)URL脱敏显示**: + - SMTP服务器地址中间部分用***代替(如 free***ing.com) + - 保护服务器信息安全,避免完整URL暴露 +- ✨ **线路2升级为VIP专属邮箱**: + - 名称改为「✨ VIP 专属邮箱」,添加VIP标签和👑皇冠图标 + - 采用紫色主题(#9B59B6),深浅模式均有专属背景色 + - 锁定无法选择,右侧显示🔒锁定图标 + - 隐藏详细配置信息(不显示用户名和服务器地址) + - 副标题提示「高级会员专属线路,更稳定快速」 +- 🔧 **新增URL脱敏方法**:`_maskUrl()` 对长URL进行中间部分遮蔽处理 +- 📐 **布局优化**:收件人邮箱输入框移至发送线路选择上方,符合用户操作流程(先填写收件人,再选择线路) + +#### 修改文件 +- `lib/src/widgets/recipe_detail/interaction/recipe_email_button.dart` — 线路选择UI重构+VIP样式+URL脱敏 + +## [0.99.22] - 2026-04-21 + +### ✨ 新功能 — 美食年轮:可视化美食旅程档案 + +#### 修复 +- 🐛 修复美食年轮页面数据不实时更新的问题:用 Obx 包裹内容区域,通过迭代 RxList 和访问 RxMap.values 注册响应式依赖(.length/.count 不会触发 GetX 响应式通知) +- 🐛 修复浏览记录/笔记/分享记录数据丢失的竞态条件:控制器使用 lazyPut 延迟初始化,onInit 中异步加载历史数据未完成时 addHistory/addNote/addRecord 已执行,导致空列表保存覆盖旧数据。添加 Completer 确保初始化完成后才执行写操作 +- 🐛 修复 RecipeDetailController._recordBrowseHistory 未 await addHistory 的问题 +- 🐛 修复月度足迹收藏数显示0的问题:FeedItemModel.fromJson 反序列化时缺少 'createdAt' key 匹配(保存用 'createdAt',读取只查 'created_at'/'post_time'),导致从 Hive 加载后 createdAt 为 null + +#### 修改内容 +- ✨ **美食年轮页面**: + - 新增「美食年轮」页面,聚合浏览/收藏/笔记/评分数据,生成可视化美食旅程档案 + - 🎯 **Hero总览区**:圆形年轮动画展示使用天数,外圈随浏览量动态增长 + - 📊 **数据总览**:4格统计卡片(浏览菜谱数/收藏数/笔记数/评分数) + - 🧬 **味觉DNA**:6维雷达图(辣/甜/咸/酸/鲜/香),基于口味偏好设置和浏览数据推断,显示味觉人格类型 + - 🏆 **美食里程碑**:9项成就时间线(初识美食/探索者/资深食客/百科/心动时刻/收藏家/记录者/品鉴师/全能美食家) + - 📅 **月度足迹**:最近3个月活动摘要,含浏览/收藏/笔记/评分数量和热门分类 + - 🍜 **菜系探索度**:环形图展示已探索菜系种类和占比 + - 入口位于设置页「🚀 功能」区域,图标 CupertinoIcons.circle_grid_3x3 + +#### 修改文件 +- `lib/src/pages/profile/data/food_timeline_page.dart` — 新建,美食年轮页面 +- `lib/src/config/app_routes.dart` — 新增 foodTimeline 路由定义和页面注册 +- `lib/src/pages/profile/profile_settings.dart` — 功能区域新增美食年轮入口 + +## [0.99.21] - 2026-04-21 + +### 🖥️ Windows 桌面端修复 — 图标清晰度/窗口宽度/中文乱码 + +#### 修改内容 +- 🎨 **图标清晰度修复**: + - 从 `icon_1024x1024.png` 重新生成 ICO,包含 17 种尺寸(16~256px) + - 窗口类注册改用 `WNDCLASSEX` + `RegisterClassEx`,分别设置 `hIcon` 和 `hIconSm` + - `LoadImage` 显式加载 256×256 大图标 + 48×48 小图标,替代 `LoadIcon`(仅 32×32) + - 窗口创建后通过 `WM_SETICON` 设置 `ICON_BIG` 和 `ICON_SMALL` + - DPI 变化时(`WM_DPICHANGED`)重新加载图标 + - 安装脚本添加 `ie4uinit.exe -show` 自动刷新图标缓存 +- 📐 **窗口加宽**: + - 默认宽度从 420px 调整为 520px + - 最小宽度从 360px 调整为 480px +- 🔤 **中文乱码修复**: + - CMakeLists.txt 添加 `/utf-8` 编译选项,解决 MSVC 默认 GBK 编码导致中文标题乱码 +- 📦 **打包配置修复**: + - Inno Setup 压缩格式改为 `lzma2/max`(兼容 6.7.1) + - 权限改为 `PrivilegesRequired=lowest`(兼容 6.7.1) + - 下载并安装 ChineseSimplified.isl 中文语言包 + +#### 修改文件 +- `windows/runner/win32_window.cpp` — 图标加载改用 LoadImage 256px + WM_SETICON + DPI 重载 +- `windows/runner/main.cpp` — 窗口宽度 420→520 +- `windows/CMakeLists.txt` — 添加 /utf-8 编译选项 +- `windows/runner/resources/app_icon.ico` — 重新生成 17 尺寸 ICO +- `installer.iss` — 修复兼容性问题 + 添加图标缓存刷新代码 + +## [0.99.20] - 2026-04-21 + +### 🖥️ Windows 桌面端适配 — 窗口尺寸/图标/名称/交互/打包 + +#### 修改内容 +- 🖥️ **窗口尺寸改为竖屏**: + - 默认窗口从 1280×720(横屏)改为 420×800(手机比例竖屏) + - 窗口启动自动居中显示 + - 添加最小窗口尺寸限制(360×600),防止窗口过小导致布局异常 +- 🎨 **应用图标和名称**: + - 将 `assets/icons/icon_1024x1024.png` 转换为 ICO 替换 Windows 默认图标 + - 窗口标题从 `mom_kitchen` 改为 `小妈厨房` + - EXE 元数据更新:ProductName=小妈厨房,CompanyName=微风暴工作室 +- 🖱️ **鼠标拖动滚动**: + - 添加 `DesktopScrollBehavior`,支持鼠标拖拽滚动列表 + - 同时支持触控板和触控笔拖拽 +- ⌨️ **桌面端返回导航**: + - 支持 `Alt+←` 键返回上一页 + - 支持 `Backspace` 键返回上一页 + - 支持 `BrowserBack` 键返回(鼠标侧键 XButton1) + - 支持 `BrowserForward` 键前进(鼠标侧键 XButton2) + - C++ 层处理 `WM_XBUTTONUP` 消息,将鼠标侧键转换为浏览器导航键 +- 📦 **打包配置**: + - 创建 Inno Setup 安装脚本 `installer.iss`,生成单个安装程序 EXE + - 配置 `distribute_options.yaml` 支持 flutter_distributor 打包 + - 安装程序支持中文界面、桌面快捷方式、安装后自动启动 + +#### 修改文件 +- `windows/runner/main.cpp` — 窗口尺寸改为 420×800,标题改为「小妈厨房」 +- `windows/runner/win32_window.cpp` — 窗口居中 + 最小尺寸限制(WM_GETMINMAXINFO) +- `windows/runner/flutter_window.cpp` — 鼠标侧键支持(WM_XBUTTONUP) +- `windows/runner/Runner.rc` — EXE 元数据更新 +- `windows/runner/resources/app_icon.ico` — 替换为应用图标 +- `lib/main.dart` — 添加 DesktopScrollBehavior + KeyboardListener(快捷键返回) +- `installer.iss` — 新建,Inno Setup 安装脚本 +- `distribute_options.yaml` — 新建,flutter_distributor 配置 + +## [0.99.19] - 2026-04-21 + +### ✨ 新功能 — 个人中心:用户卡片改为开发计划卡片 + +#### 修改内容 +- ✨ **开发计划卡片**: + - 用户信息卡片替换为「开发计划」卡片,点击弹出 Cupertino Sheet + - Sheet 展示 12 项开发路线图,含状态标签(即将上线/开发中/规划中) + - 路线图内容:多端适配、用户中心、工具中心、导出功能、菜品投稿、社区互动、智能推荐、食材管理、营养分析、多语言支持、云同步、消息通知 + - 移除旧的登录/编辑/个性化按钮(登录功能暂不开发) + - 移除 ProfileController 和 PersonalizationPage 依赖 + +#### 修改文件 +- `lib/src/pages/profile/profile_home.dart` — 用户卡片改为开发计划卡片 + Sheet 弹窗 + ## [0.99.18] - 2026-04-20 ### ✨ 新功能 — 今天吃什么:动态筛选(选项越多,匹配越少) @@ -95,192 +315,15 @@ All notable changes to this project will be documented in this file. - `pubspec.yaml` — 删除receive_sharing_intent依赖 -## [0.99.15] - 2026-04-20 - -### ✨ 新功能 — 公告页面 - -#### 功能描述 -- 新增「📢 公告」页面,展示系统公告列表 -- API接口: `https://yy.vogov.cn/api/app/notice_api.php` -- 支持下拉刷新(CupertinoSliverRefreshControl) -- 四种状态展示:加载中 / 加载失败(含重试) / 暂无公告 / 公告列表 -- 公告卡片含编号、内容、作者、创建/更新时间 -- 列表底部"到底了"提示 -- 个人中心「营销信息」改为「📢 公告」,点击跳转公告页面 -- 全部使用 DesignTokens,深色模式完整支持 - -#### 修改文件 -- `lib/src/pages/profile/info/notice_page.dart` — 新增公告页面 -- `lib/src/config/api_config.dart` — 新增 noticeBaseUrl 和 notice 接口配置 -- `lib/src/config/app_routes.dart` — 添加路由 /notice -- `lib/src/pages/profile/profile_home.dart` — 营销信息改为公告入口 - - -## [0.99.14] - 2026-04-20 - -### ✨ 新功能 — 外卖备注工具 - -#### 功能描述 -- 新增「🛵 外卖备注」工具页面,支持管理外卖常用备注 -- 三大分类:🌶️ 口味、🛵 配送、🎭 整活,各含默认备注模板 -- 口味:少盐、少油、少糖、微辣、不要辣等 14 条 -- 配送:放门口、不要敲门、尽快送达等 11 条 -- 整活:画个笑脸、写句鼓励的话等 10 条 -- 支持选择备注 → 一键生成组合备注 → 复制到剪贴板 -- 支持添加/编辑/删除自定义备注 -- 偏好设置页「偏好摘要」新增「添加备注」按钮,一键将偏好摘要写入外卖备注 - -#### 修改文件 -- `lib/src/pages/tools/cooking/takeout_note_page.dart` — 新增外卖备注页面(分类管理、默认备注、增删改查、一键生成) -- `lib/src/models/app/tool_item_model.dart` — ToolRegistry 注册新工具 -- `lib/src/config/app_routes.dart` — 添加路由 /tools/takeout-note -- `lib/src/pages/profile/settings/preference_page.dart` — 偏好摘要增加一键添加备注按钮 - - -## [0.99.13] - 2026-04-20 - -### ✨ 新功能 — 菜谱外部搜索按钮 + 搜索后缀选择 - -#### 功能描述 -- 在菜谱详情页邮件按钮右侧新增「🔍 搜索」按钮(按钮移至相似菜品上方) -- 点击弹出 Cupertino 风格底部弹窗,展示搜索引擎选择列表 -- 支持 4 个搜索引擎:百度、Google、Bing、DuckDuckGo(2x2 网格布局) -- 新增搜索后缀选择标签:👨‍🍳怎么做、😋好吃吗、💪功效、🍽️相关菜品、✏️自定义 -- 选择后缀后搜索关键词自动拼接(如「番茄炒蛋 怎么做」) -- 自定义后缀支持输入任意关键词 -- 预览卡片实时显示最终搜索关键词 -- 选择后使用 `url_launcher` 在外部浏览器中搜索 - -#### 修改文件 -- `lib/src/widgets/recipe_detail/interaction/recipe_email_button.dart` — 新增 SearchEngine 枚举 + 搜索按钮 + 搜索后缀选择 + 2x2 网格 + 浏览器跳转 -- `lib/src/pages/home/recipe_detail_page.dart` — 邮件+搜索按钮移至相似菜品上方 - - -## [0.99.12] - 2026-04-19 - -### 🐛 Bug 修复 — 邮件发送SSL握手失败 + VPN代理检测 + 连接容错 - -#### 问题描述 -- **465/SSL HandshakeException**:`SecureSocket.connect`直接SSL握手失败,延迟SSL升级也失败后无回退 -- **587/STARTTLS Socket关闭**:VPN代理拦截SMTP流量,服务器不发问候就关闭连接 -- **DNS解析到VPN代理IP**:`198.18.14.246`等虚拟IP,代理不正确转发SMTP协议 -- **关闭VPN代理后邮件发送成功**:确认根因为VPN/代理拦截SMTP连接 - -#### 修复方案 -- 📝 **修改 `packages/mailer/lib/src/smtp/connection.dart`**: - - SSL直连失败 → 延迟SSL升级 → 升级也失败则回退到明文连接(由STARTTLS机制处理加密) - - 三级容错策略:`SecureSocket.connect` → `Socket.connect + SecureSocket.secure` → `Socket.connect(明文)` -- 📝 **修改 `lib/src/services/data/business/email_service.dart`**: - - DNS预解析新增VPN/代理IP检测:`198.18.x`/`10.x`/`172.16.x`/`127.x` 等代理地址段 - - 检测到代理IP时输出警告日志,建议关闭VPN - - `MailerException` 细化错误提示:`SmtpUnsecureException`/`SmtpClientAuthenticationException`/`SmtpNoGreetingException`/`SmtpClientCommunicationException` 各有专属提示 - - `HandshakeException` 和 `Socket was closed` 错误提示增加"建议关闭VPN后重试" - -#### 修改文件 -- `packages/mailer/lib/src/smtp/connection.dart` — SSL三级容错策略 -- `lib/src/services/data/business/email_service.dart` — VPN检测 + 错误提示优化 - - -## [0.99.11] - 2026-04-19 - -### 🐛 Bug 修复 — 邮件发送全链路修复(fallback配置/allowInsecure/限额计数/超时) - -#### 问题描述 -- **邮件发送持续失败**:服务器正常但所有端口尝试均失败 -- **根因分析**(全链路追踪 调用端→发送端→结果端): - 1. `587/SSL` fallback配置根本性错误:587端口是STARTTLS端口,不支持直接SSL连接,`ssl:true`导致SecureSocket握手必然失败 - 2. `25/明文` fallback无实际意义:绝大多数ISP封禁25端口防垃圾邮件 - 3. `allowInsecure: !ssl` 逻辑陷阱:STARTTLS fallback时`allowInsecure=true`,若服务器不支持STARTTLS则明文发送凭证,现代SMTP服务器会拒绝 - 4. QQ邮箱使用账号密码而非授权码:QQ SMTP需要设置中生成的授权码,账号密码会触发认证失败 - 5. 每日限额计数包含`sending`状态:app崩溃后残留的sending记录占额度,导致限额耗尽 - 6. 无发送超时配置:移动网络下默认60秒等待过长 - -#### 修复方案 -- 📝 **修改 `email_service.dart`**: - - 移除`587/SSL`和`25/明文` fallback,仅保留`587/STARTTLS`作为有效备选 - - `allowInsecure`统一设为`false`:确保STARTTLS必须升级成功,否则抛出`SmtpUnsecureException`而非明文传凭证 - - QQ邮箱密码改为授权码占位提示 - - 自定义SMTP fallback同步移除`587/SSL` - - 添加`timeout: Duration(seconds: 30)`发送超时 - - 新增`TimeoutException`专门捕获,提示连接超时 -- 📝 **修改 `email_history_controller.dart`**: - - `todaySentCount`仅计`success`状态,排除`sending`(崩溃残留)和`failed` - -#### 修改文件 -- `lib/src/services/data/business/email_service.dart` — fallback修正 + allowInsecure修复 + 超时配置 + QQ授权码 -- `lib/src/controllers/user/email_history_controller.dart` — 限额计数仅计success - - -## [0.99.10] - 2026-04-19 - -### 🐛 Bug 修复 — 邮件发送 HandshakeException 深度修复 - -#### 问题描述 -- **465端口SSL握手持续失败**:`HandshakeException: Connection terminated during handshake` -- **587端口STARTTLS也失败**:`Socket was closed even though a response was expected` -- **根因分析**: - 1. `ignoreBadCertificate: true` 仅控制证书验证,无法修复TLS协议层握手失败 - 2. `SecureSocket.connect` 在Android设备上直接进行TLS握手,某些服务器/网络环境下握手被中断 - 3. 之前只有单级备选(465→587),无法覆盖587端口SSL模式等场景 - -#### 修复方案 -- 📝 **修改 `packages/mailer/lib/src/smtp/connection.dart`**: - - `connect()` 方法新增 HandshakeException 容错:SSL直连失败后,先建立明文TCP连接,延迟200ms后再升级为SSL - - 此方案绕过 `SecureSocket.connect` 的TLS握手问题,通过分步连接给服务器更多准备时间 -- 📝 **修改 `lib/src/services/data/business/email_service.dart`**: - - `SmtpRoute` 重构:`fallbackPort`/`fallbackSsl` 替换为 `List` 多级备选 - - 新增 `SmtpFallback` 类:支持 port/ssl/label 三元组配置 - - 预设线路1新增3级备选:587/STARTTLS → 587/SSL → 25/明文 - - 预设线路2新增2级备选:587/STARTTLS → 587/SSL - - `sendRecipeEmailCustom` 自定义SMTP也支持自动备选端口 - - `HandshakeException` 错误提示更精确:标注"TLS协议层错误(非证书问题)" - - `SocketException` 新增连接拒绝识别:区分端口未开放 vs 网络故障 - -#### 修改文件 -- `packages/mailer/lib/src/smtp/connection.dart` — SSL连接容错:HandshakeException后分步重连 -- `lib/src/services/data/business/email_service.dart` — 多级fallback + 自定义SMTP备选 + 错误提示优化 - - -## [0.99.9] - 2026-04-19 - -### 🐛 Bug 修复 — 邮件发送 HandshakeException - -#### 问题描述 -- **官方线路发送邮件失败**:连接 SMTP 服务器 465 端口时抛出 `HandshakeException: Connection terminated during handshake` -- **根因分析**: - 1. `SmtpServer` 未设置 `ignoreBadCertificate: true`,证书验证失败 - 2. 更深层原因:465端口(隐式SSL)的TLS握手在Android设备上可能因服务器TLS配置变更/网络中间设备干扰而失败,`ignoreBadCertificate`仅控制证书验证,无法修复TLS协议层握手失败 - 3. 587端口(STARTTLS)通常更稳定,因为先建立明文连接再升级加密 - -#### 修复方案 -- 📝 **修改 `email_service.dart`**: - - `SmtpServer` 添加 `ignoreBadCertificate: true` - - `SmtpRoute` 新增 `fallbackPort`/`fallbackSsl` 备选端口配置 - - 预设线路添加587端口(STARTTLS)作为备选 - - `sendRecipeEmail` 主端口发送失败后自动尝试备选端口 - - 新增 `HandshakeException` 专门捕获,提供更清晰的错误提示 - -#### 修改文件 -- `lib/src/services/data/business/email_service.dart` — SmtpServer ignoreBadCertificate + 备选端口 + 自动重试 - - -## [0.99.8] - 2026-04-19 - -### 🔧 重构 — 移除 flutter_dotenv 依赖 - -#### 变更说明 -- **移除 flutter_dotenv 库**:不再需要运行时加载 `.env` 文件,SMTP凭证改为代码直接配置 -- **删除 `.env.example` 模板文件**:不再需要环境变量模板 -- **移除 `.env` 资源声明**:pubspec.yaml 中不再将 `.env` 作为 asset 打包 - -#### 修改文件 -- `pubspec.yaml` — 移除 `flutter_dotenv: ^5.2.1` 依赖,移除 `.env` 资源声明 -- `lib/main.dart` — 移除 `dotenv.load()` 初始化代码和 import -- `lib/src/services/data/business/email_service.dart` — SMTP凭证从 `dotenv.env[...]` 改为直接硬编码配置 -- `.env.example` — 删除(不再需要) - - -> 📌 已移除较早版本记录(0.99.7及之前),功能已归档至软件特性清单。 +> 📌 已移除较早版本记录(0.99.15及之前),功能已归档至软件特性清单。 +> - 0.99.15: 公告页面(列表+下拉刷新+四种状态) +> - 0.99.14: 外卖备注工具(口味/配送/整活分类+一键生成) +> - 0.99.13: 菜谱外部搜索按钮+搜索后缀选择 +> - 0.99.12: 邮件发送SSL握手失败+VPN代理检测+连接容错 +> - 0.99.11: 邮件发送全链路修复(fallback配置/allowInsecure/限额计数/超时) +> - 0.99.10: 邮件发送HandshakeException深度修复 +> - 0.99.9: 邮件发送HandshakeException+备选端口 +> - 0.99.8: 移除flutter_dotenv依赖 > - 0.99.7: 邮件分享系统升级(HTML模板+限额+记录管理) > - 0.99.6: 帮我做决定转盘指针指向分界线修复 > - 0.99.5: 发现页面Dismissible组件错误修复 diff --git a/README.md b/README.md index d46ad08..e11d94c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,54 @@ 一款基于 Flutter 的 iOS26 风格美食应用,支持多平台(Android/iOS/HarmonyOS/Web) +## 📱 APP 介绍 + +### 一句话介绍(10条) +1. 小妈厨房:基于 Flutter 的 iOS26 风格美食应用,支持多平台,集成个性化设置、热量追踪、智能购物清单、瀑布流发现页和农场游戏等功能。 +2. 小妈厨房:你的私人美食顾问,提供多平台支持、个性化设置、营养分析、智能购物清单、瀑布流发现和农场游戏等全方位服务。 +3. 小妈厨房:iOS26 风格的多平台美食应用,集个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏于一体。 +4. 小妈厨房:支持多平台的美食应用,提供个性化设置、热量追踪、营养分析、智能购物清单、瀑布流发现和农场游戏功能。 +5. 小妈厨房:基于 Flutter 开发的 iOS26 风格美食应用,支持多平台,包含个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏。 +6. 小妈厨房:多平台美食应用,提供个性化设置、热量追踪、营养分析、智能购物清单、瀑布流发现和农场游戏等全面功能。 +7. 小妈厨房:iOS26 风格的美食应用,支持多平台,集成个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏。 +8. 小妈厨房:基于 Flutter 的多平台美食应用,提供个性化设置、热量追踪、营养分析、智能购物清单、瀑布流发现和农场游戏。 +9. 小妈厨房:支持多平台的 iOS26 风格美食应用,集个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏于一体。 +10. 小妈厨房:你的全方位美食助手,支持多平台,提供个性化设置、热量追踪、营养分析、智能购物清单、瀑布流发现和农场游戏。 + +### APP 关键字(20个) +美食应用, 食谱管理, iOS26风格, Flutter, 多平台, 个性化设置, 热量追踪, 营养分析, 智能购物清单, 瀑布流布局, 农场游戏, 烹饪工具, 多语言支持, 智能推荐, 健康饮食, 食材管理, 食谱搜索, 美食灵感, 生活方式, 家庭烹饪 + +### 50字内介绍(5条) +1. 小妈厨房是基于 Flutter 开发的 iOS26 风格美食应用,支持多平台,集成个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏等功能。 +2. 小妈厨房是一款支持多平台的美食应用,采用 iOS26 风格设计,提供个性化设置、热量追踪、营养分析、智能购物清单、瀑布流发现和农场游戏。 +3. 基于 Flutter 的小妈厨房是 iOS26 风格的多平台美食应用,集成个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏等全面功能。 +4. 小妈厨房是支持多平台的 iOS26 风格美食应用,基于 Flutter 开发,提供个性化设置、热量追踪、营养分析、智能购物清单、瀑布流发现和农场游戏。 +5. 小妈厨房是一款多平台美食应用,采用 iOS26 风格设计,基于 Flutter 开发,集成个性化设置、热量追踪、智能购物清单、瀑布流发现和农场游戏。 + +### 100字内介绍(5条) +1. 小妈厨房是基于 Flutter 开发的 iOS26 风格美食应用,支持 Android、iOS、HarmonyOS 和 Web 多平台运行。它集成了个性化设置(主题颜色、字体大小、深色模式等)、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等全面功能,为用户提供全方位的美食体验。 +2. 作为一款支持多平台的美食应用,小妈厨房采用 iOS26 风格设计,基于 Flutter 开发。它提供个性化设置、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等功能,满足用户从食谱发现到食材采购的全方位需求。 +3. 小妈厨房是 iOS26 风格的多平台美食应用,基于 Flutter 开发。它集成了个性化设置、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等功能,为用户提供从食谱管理到健康饮食的全面服务。 +4. 基于 Flutter 开发的小妈厨房是一款支持多平台的 iOS26 风格美食应用。它提供个性化设置、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等功能,为用户打造一站式的美食体验。 +5. 小妈厨房是一款支持多平台的美食应用,采用 iOS26 风格设计,基于 Flutter 开发。它集成了个性化设置、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等功能,为用户提供全方位的美食服务。 + +### 300-500字介绍(2条) +1. 小妈厨房是一款基于 Flutter 开发的现代化美食应用,采用 iOS26 风格设计,支持 Android、iOS、HarmonyOS 和 Web 多平台运行。它集个性化设置、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等全面功能于一体,为用户提供全方位的美食体验。 + + 应用的个性化设置系统支持主题颜色、字体大小、深色模式、动画强度和多语言等多种自定义选项,让每个用户都能打造专属于自己的美食世界。热量追踪与营养分析功能帮助用户监控饮食健康,智能购物清单让食材采购变得更加高效,瀑布流发现页则为用户推荐个性化的食谱和食材,农场游戏功能则增添了烹饪的乐趣。 + + 小妈厨房注重用户体验,采用响应式设计,适配不同屏幕尺寸。它支持多语言(简体中文、繁体中文、English),满足全球用户的需求。应用采用现代化的技术架构,包括 GetX 状态管理、本地缓存、后台静默加载和错误处理机制,确保应用运行流畅稳定。 + + 无论是烹饪新手还是美食达人,小妈厨房都能为你提供专业的美食服务,让烹饪变得简单、有趣又健康。 + +2. 小妈厨房是一款专为美食爱好者打造的应用,采用 iOS26 风格设计,基于 Flutter 开发,支持多平台运行。它以用户为中心,集成了个性化设置、热量追踪与营养分析、智能购物清单、瀑布流发现页和农场游戏等全面功能,为用户提供一站式的美食解决方案。 + + 应用的个性化设置系统允许用户自定义主题颜色、字体大小、深色模式、动画强度和语言等,打造专属于自己的美食世界。热量追踪与营养分析功能帮助用户监控饮食健康,智能购物清单让食材采购变得更加高效,瀑布流发现页为用户推荐个性化的食谱和食材,农场游戏功能则增添了烹饪的乐趣。 + + 小妈厨房采用现代化的技术架构,包括 GetX 状态管理、本地缓存、后台静默加载和错误处理机制,确保应用运行流畅稳定。它支持多语言,满足全球用户的需求,采用响应式设计,适配不同屏幕尺寸。 + + 无论是寻找新食谱、管理食材、追踪营养,还是体验农场游戏的乐趣,小妈厨房都能为你提供全方位的服务,让烹饪成为一种享受,让美食成为生活的亮点。 + --- ## 🎨 个性化设置 diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 8ed3768..4158e10 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -1,5 +1,5 @@ # ======================================== -# Mom's Kitchen - Android Proguard Rules +# cute Kitchen - Android Proguard Rules # 修复 release 包缓存失效、图片加载不出的问题 # 创建: 2026-04-19 # ======================================== diff --git a/dist/MomKitchen_Setup_0.99.1.exe b/dist/MomKitchen_Setup_0.99.1.exe new file mode 100644 index 0000000..edd7048 Binary files /dev/null and b/dist/MomKitchen_Setup_0.99.1.exe differ diff --git a/dist/小妈厨房_Setup_1.0.3.exe b/dist/小妈厨房_Setup_1.0.3.exe new file mode 100644 index 0000000..e8b4c88 Binary files /dev/null and b/dist/小妈厨房_Setup_1.0.3.exe differ diff --git a/distribute_options.yaml b/distribute_options.yaml new file mode 100644 index 0000000..244cf8d --- /dev/null +++ b/distribute_options.yaml @@ -0,0 +1,29 @@ +# ============================================================================ +# 小妈厨房 - flutter_distributor 打包配置 +# ============================================================================ +# 使用方法: +# 1. 先安装 flutter_distributor: dart pub global activate flutter_distributor +# 2. 确保 E:\cache\pub\bin 在系统 PATH 中 +# 3. 先安装 Inno Setup: https://jrsoftware.org/isdl.php +# 4. 构建+打包一条命令: +# flutter_distributor package --platform windows --target inno +#- 键盘快捷键 : Alt+← / Backspace / BrowserBack 键返回上一页 +#- 鼠标侧键 :C++ 层处理 WM_XBUTTONUP ,XButton1=返回,XButton2=前进 + + +# 输出: dist/ 目录下生成安装程序 EXE +# ============================================================================ + +# 安装包输出目录 +output: dist/ + +releases: + - name: release + jobs: + - name: windows-inno # 打包任务名称 + package: + platform: windows # 目标平台: windows + target: inno # 打包格式: inno (Inno Setup 安装程序) + build_args: + dart-define: + - BUILD_MODE=release # Dart 编译常量,代码中可通过 String.fromEnvironment 读取 diff --git a/docs/design/1.jpg b/docs/design/1.jpg new file mode 100644 index 0000000..118f256 Binary files /dev/null and b/docs/design/1.jpg differ diff --git a/docs/design/2.jpg b/docs/design/2.jpg new file mode 100644 index 0000000..3e1a124 Binary files /dev/null and b/docs/design/2.jpg differ diff --git a/docs/design/3.jpg b/docs/design/3.jpg new file mode 100644 index 0000000..39b8b44 Binary files /dev/null and b/docs/design/3.jpg differ diff --git a/docs/design/4.jpg b/docs/design/4.jpg new file mode 100644 index 0000000..7a5232c Binary files /dev/null and b/docs/design/4.jpg differ diff --git a/docs/design/5.jpg b/docs/design/5.jpg new file mode 100644 index 0000000..737f986 Binary files /dev/null and b/docs/design/5.jpg differ diff --git a/docs/design/6.jpg b/docs/design/6.jpg new file mode 100644 index 0000000..2320940 Binary files /dev/null and b/docs/design/6.jpg differ diff --git a/docs/design/processed/bordered_1.jpg b/docs/design/processed/bordered_1.jpg new file mode 100644 index 0000000..c25def3 Binary files /dev/null and b/docs/design/processed/bordered_1.jpg differ diff --git a/docs/design/processed/bordered_2.jpg b/docs/design/processed/bordered_2.jpg new file mode 100644 index 0000000..a60e39b Binary files /dev/null and b/docs/design/processed/bordered_2.jpg differ diff --git a/docs/design/processed/bordered_3.jpg b/docs/design/processed/bordered_3.jpg new file mode 100644 index 0000000..b0c1c77 Binary files /dev/null and b/docs/design/processed/bordered_3.jpg differ diff --git a/docs/design/processed/bordered_4.jpg b/docs/design/processed/bordered_4.jpg new file mode 100644 index 0000000..f2111d4 Binary files /dev/null and b/docs/design/processed/bordered_4.jpg differ diff --git a/docs/design/processed/bordered_5.jpg b/docs/design/processed/bordered_5.jpg new file mode 100644 index 0000000..5418fcb Binary files /dev/null and b/docs/design/processed/bordered_5.jpg differ diff --git a/docs/design/processed/portrait/bordered_portrait_1.jpg b/docs/design/processed/portrait/bordered_portrait_1.jpg new file mode 100644 index 0000000..c98449c Binary files /dev/null and b/docs/design/processed/portrait/bordered_portrait_1.jpg differ diff --git a/docs/design/processed/portrait/bordered_portrait_2.jpg b/docs/design/processed/portrait/bordered_portrait_2.jpg new file mode 100644 index 0000000..59b219a Binary files /dev/null and b/docs/design/processed/portrait/bordered_portrait_2.jpg differ diff --git a/docs/design/processed/portrait/bordered_portrait_3.jpg b/docs/design/processed/portrait/bordered_portrait_3.jpg new file mode 100644 index 0000000..9053317 Binary files /dev/null and b/docs/design/processed/portrait/bordered_portrait_3.jpg differ diff --git a/docs/design/processed/portrait/bordered_portrait_4.jpg b/docs/design/processed/portrait/bordered_portrait_4.jpg new file mode 100644 index 0000000..e3014b4 Binary files /dev/null and b/docs/design/processed/portrait/bordered_portrait_4.jpg differ diff --git a/docs/design/processed/portrait/bordered_portrait_5.jpg b/docs/design/processed/portrait/bordered_portrait_5.jpg new file mode 100644 index 0000000..2a31469 Binary files /dev/null and b/docs/design/processed/portrait/bordered_portrait_5.jpg differ diff --git a/docs/superpowers/specs/2026-04-08-refactor-design.md b/docs/superpowers/specs/2026-04-08-refactor-design.md index f3795b9..cb99775 100644 --- a/docs/superpowers/specs/2026-04-08-refactor-design.md +++ b/docs/superpowers/specs/2026-04-08-refactor-design.md @@ -2,7 +2,7 @@ ## 概述 -本设计文档描述了对 Mom's Kitchen 项目进行全面重构的方案,包括: +本设计文档描述了对 Cute Kitchen 项目进行全面重构的方案,包括: 1. GetX 全局状态管理 2. 组件使用 PageStandards 3. 路由守卫系统 diff --git a/download.html b/download.html new file mode 100644 index 0000000..275c3bd --- /dev/null +++ b/download.html @@ -0,0 +1,373 @@ + + + + + +小妈厨房 - 下载 + + + + + +
+ + + +
+ +
+ 小妈厨房 +
+
+

小妈厨房

+ ✦ v0.99.1 +
+

每一餐,都是家的味道 🍳

+

从「今天吃什么」的纠结中解放,智能推荐搭配海量菜谱,让每一顿饭都充满家的温暖。食材管理、烹饪技巧,一站式解决厨房大小事。

+
+
+ +
+
✨ 特色功能
+
+
+ 🤔 +

今天吃什么

+

智能推荐,告别选择困难

+
+
+ 📖 +

菜谱大全

+

海量菜谱,步骤详细易懂

+
+
+ 🛒 +

食材管理

+

冰箱有什么,一查便知

+
+
+ 💡 +

厨房小贴士

+

烹饪技巧,新手也能变大厨

+
+
+
+ +
+
📱 应用预览
+
+
预览
+
预览
+
🧑‍🍳
+
🥬
+
📝
+
+
+ + + + + +
+ + + diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..0ba9d7c --- /dev/null +++ b/installer.iss @@ -0,0 +1,77 @@ +; ============================================================================ +; 小妈厨房 - Inno Setup 安装脚本 +; ============================================================================ +; 使用方法: +; 方式一(推荐,自动读取版本号): +; .\scripts\package_windows.ps1 +; 方式二(手动指定版本号): +; & "d:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DAppVer=1.0.0 installer.iss +; 方式三(flutter_distributor): +; $env:PATH = "E:\cache\pub\bin;" + $env:PATH +; flutter_distributor package --platform windows --target inno +; +; 前置条件: +; 1. 已安装 Inno Setup 6: https://jrsoftware.org/isdl.php +; 2. 已下载中文语言包到 Inno Setup Languages 目录 +; 3. 已构建 Flutter 应用: flutter build windows +; ============================================================================ + +; ---- 动态版本号(通过 ISCC 命令行 /DAppVer=x.x.x 传入) ---- +; 由 package_windows.ps1 自动传递,无需手动设置 +#ifndef AppVer + #define AppVer "0.0.0" +#endif + +[Setup] +; ---- 应用信息 ---- +AppName=小妈厨房 +AppVersion={#AppVer} +AppPublisher=微风暴工作室 +AppPublisherURL=https://www.wktyl.com +AppSupportURL=https://www.wktyl.com + +; ---- 安装路径 ---- +DefaultDirName={autopf}\cuteKitchen +DefaultGroupName=小妈厨房 +UninstallDisplayName=小妈厨房 + +; ---- 输出配置 ---- +OutputDir=dist +OutputBaseFilename=小妈厨房_Setup_{#AppVer} + +; ---- 压缩配置 ---- +Compression=lzma2/max +SolidCompression=yes + +; ---- 图标和权限 ---- +SetupIconFile=windows\runner\resources\app_icon.ico +PrivilegesRequired=lowest +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible + +[Languages] +Name: "chinesesimplified"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" + +[Tasks] +Name: "desktopicon"; Description: "创建桌面快捷方式"; GroupDescription: "附加图标:"; Flags: unchecked + +[Files] +Source: "build\windows\x64\runner\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs + +[Icons] +Name: "{group}\小妈厨房"; Filename: "{app}\mom_kitchen.exe" +Name: "{autodesktop}\小妈厨房"; Filename: "{app}\mom_kitchen.exe"; Tasks: desktopicon + +[Run] +Filename: "{app}\mom_kitchen.exe"; Description: "启动小妈厨房"; Flags: nowait postinstall skipifsilent + +[Code] +procedure CurStepChanged(CurStep: TSetupStep); +var + ResultCode: Integer; +begin + if CurStep = ssPostInstall then + begin + Exec('ie4uinit.exe', '-show', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + end; +end; diff --git a/lib/PAGE_STANDARDS.md b/lib/PAGE_STANDARDS.md index 6b16e1a..4c9754d 100644 --- a/lib/PAGE_STANDARDS.md +++ b/lib/PAGE_STANDARDS.md @@ -545,4 +545,4 @@ lib/src/standards/ --- **最后更新时间:** 2026-04-08 -**维护者:** Mom's Kitchen 开发团队 +**维护者:** Cute Kitchen 开发团队 diff --git a/lib/README.md b/lib/README.md index e388f68..3bcbedd 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1,4 +1,4 @@ -# Mom's Kitchen 项目基础支撑库说明 +# Cute Kitchen 项目基础支撑库说明 ## 项目架构 @@ -557,4 +557,4 @@ import 'package:mom_kitchen/src/services/app_service.dart'; --- **最后更新时间:** 2026-04-08 -**维护者:** Mom's Kitchen 开发团队 +**维护者:** Cute Kitchen 开发团队 diff --git a/lib/main.dart b/lib/main.dart index 8677d74..fe16dea 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,9 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:catcher_2/catcher_2.dart'; import 'package:mom_kitchen/src/l10n/app_localizations.dart'; @@ -42,7 +45,21 @@ void main() { }; FlutterError.onError = (details) { - if (kReleaseMode && _isLayoutOverflow(details)) { + if (_isLayoutOverflow(details)) { + debugPrint('⚠️ [LayoutWarning] ${details.exceptionAsString()}'); + if (details.stack != null) { + debugPrint('⚠️ [LayoutWarning] Stack: ${details.stack}'); + } + if (!kReleaseMode) { + FlutterError.presentError(details); + } + try { + Catcher2.reportCheckedError(details.exception, details.stack); + } catch (_) { + debugPrint( + '❌ FlutterError.onError catch failed: ${details.exception}', + ); + } return; } FlutterError.presentError(details); @@ -113,6 +130,16 @@ Future _initApp() async { await OrientationService().unlockOrientation(); } +class DesktopScrollBehavior extends MaterialScrollBehavior { + @override + Set get dragDevices => { + PointerDeviceKind.touch, + PointerDeviceKind.mouse, + PointerDeviceKind.stylus, + PointerDeviceKind.trackpad, + }; +} + class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -144,13 +171,37 @@ class MyApp extends StatelessWidget { getPages: AppRoutes.pages, initialBinding: AppBinding(), builder: (context, widget) { - return MediaQuery( - data: MediaQuery.of( - context, - ).copyWith(textScaler: TextScaler.linear(textScale)), - child: ColoredBox( - color: themeService.backgroundColor.value, - child: widget ?? const SizedBox.shrink(), + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: (event) { + if (event is KeyDownEvent) { + final isAltLeft = + HardwareKeyboard.instance.isAltPressed && + event.logicalKey == LogicalKeyboardKey.arrowLeft; + final isBackspace = + event.logicalKey == LogicalKeyboardKey.backspace; + final isBrowserBack = + event.logicalKey == LogicalKeyboardKey.browserBack; + if (isAltLeft || isBackspace || isBrowserBack) { + if (Get.currentRoute != '/' && + Get.currentRoute != AppRoutes.main && + Get.currentRoute != AppRoutes.guide) { + Get.back(); + } + } + } + }, + child: ScrollConfiguration( + behavior: DesktopScrollBehavior(), + child: MediaQuery( + data: MediaQuery.of( + context, + ).copyWith(textScaler: TextScaler.linear(textScale)), + child: ColoredBox( + color: themeService.backgroundColor.value, + child: widget ?? const SizedBox.shrink(), + ), + ), ), ); }, diff --git a/lib/src/config/app_routes.dart b/lib/src/config/app_routes.dart index ee9fa19..f3e1693 100644 --- a/lib/src/config/app_routes.dart +++ b/lib/src/config/app_routes.dart @@ -54,6 +54,7 @@ import 'package:mom_kitchen/src/pages/profile/info/privacy_policy_page.dart'; import 'package:mom_kitchen/src/pages/profile/info/guide_page.dart'; import 'package:mom_kitchen/src/pages/profile/info/learn_us_page.dart'; import 'package:mom_kitchen/src/pages/profile/tools/rating_records_page.dart'; +import 'package:mom_kitchen/src/pages/profile/data/food_timeline_page.dart'; import 'package:mom_kitchen/src/pages/profile/social/email_history_page.dart'; import 'package:mom_kitchen/src/pages/profile/social/share_records_page.dart'; import 'package:mom_kitchen/src/pages/tools/cooking/calculator/date_calculator_page.dart'; @@ -143,6 +144,7 @@ class AppRoutes { static const String toolsTakeoutNote = '/tools/takeout-note'; static const String dataExport = '/data-export'; static const String ratingRecords = '/rating-records'; + static const String foodTimeline = '/food-timeline'; // 农场游戏路由 static const String farmGame = '/farm-game'; @@ -216,6 +218,11 @@ class AppRoutes { page: () => const RatingRecordsPage(), middlewares: [PageStandardsMiddleware()], ), + GetPage( + name: foodTimeline, + page: () => const FoodTimelinePage(), + middlewares: [PageStandardsMiddleware()], + ), // 农场游戏路由 GetPage( name: farmGame, diff --git a/lib/src/controllers/browse/recipe_detail_controller.dart b/lib/src/controllers/browse/recipe_detail_controller.dart index 010f983..d9f1b6a 100644 --- a/lib/src/controllers/browse/recipe_detail_controller.dart +++ b/lib/src/controllers/browse/recipe_detail_controller.dart @@ -1,4 +1,4 @@ -// 2026-04-09 | RecipeDetailController | 菜谱详情控制器 | 管理菜谱详情数据 +// 2026-04-09 | RecipeDetailController | 菜谱详情控制器 | 管理菜谱详情数据 // 2026-04-12 | 新增浏览记录功能,记录用户浏览历史 // 2026-04-13 | 新增IP状态显示,评分前加载剩余次数 import 'package:flutter/foundation.dart'; @@ -80,7 +80,7 @@ class RecipeDetailController extends BaseController { await _checkFavorite(); _recordView(); - _recordBrowseHistory(); + await _recordBrowseHistory(); _loadIpStatus(); } catch (e, stackTrace) { debugPrint('❌ 菜谱详情加载失败: $e'); @@ -158,7 +158,7 @@ class RecipeDetailController extends BaseController { } } - void _recordBrowseHistory() { + Future _recordBrowseHistory() async { if (recipe.value != null) { try { final controller = Get.find(); @@ -170,7 +170,7 @@ class RecipeDetailController extends BaseController { category: recipe.value!.categoryName, viewedAt: DateTime.now().toIso8601String(), ); - controller.addHistory(historyItem); + await controller.addHistory(historyItem); debugPrint('📝 已记录浏览历史: ${recipe.value!.title}'); } catch (e) { debugPrint('记录浏览历史失败: $e'); @@ -342,9 +342,7 @@ class RecipeDetailController extends BaseController { recipeCode: r.code, categoryName: r.categoryName, shareType: r.hasCode ? ShareType.link : ShareType.text, - shareUrl: r.hasCode - ? 'https://eat.wktyl.com/recipe/${r.code}' - : null, + shareUrl: r.hasCode ? 'https://eat.wktyl.com/recipe/${r.code}' : null, ratingScore: r.rating?.score, viewCount: viewCount.value > 0 ? viewCount.value : null, likeCount: likeCount.value > 0 ? likeCount.value : null, diff --git a/lib/src/controllers/data/browse_history_controller.dart b/lib/src/controllers/data/browse_history_controller.dart index e0fa26d..0e0b79f 100644 --- a/lib/src/controllers/data/browse_history_controller.dart +++ b/lib/src/controllers/data/browse_history_controller.dart @@ -1,8 +1,10 @@ // 2026-04-12 | BrowseHistoryController | 浏览记录控制器 | 管理用户浏览菜谱历史 +// 2026-04-21 | 修复:addHistory在loadHistory完成前执行导致数据覆盖的竞态条件 +import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'dart:convert'; import '../../models/data/record/browse_history_model.dart'; class BrowseHistoryController extends GetxController { @@ -15,10 +17,14 @@ class BrowseHistoryController extends GetxController { List get history => _history; SharedPreferences? _prefs; + Completer? _initCompleter; + + bool get _isReady => _initCompleter != null && _initCompleter!.isCompleted; @override void onInit() { super.onInit(); + _initCompleter = Completer(); _initPrefs(); } @@ -29,9 +35,16 @@ class BrowseHistoryController extends GetxController { debugPrint('浏览记录初始化完成,共 ${_history.length} 条记录'); } catch (e) { debugPrint('初始化SharedPreferences失败: $e'); + } finally { + _initCompleter?.complete(); } } + Future _ensureReady() async { + if (_isReady) return; + await _initCompleter?.future; + } + Future loadHistory() async { try { _prefs ??= await SharedPreferences.getInstance(); @@ -53,6 +66,7 @@ class BrowseHistoryController extends GetxController { Future addHistory(BrowseHistoryModel item) async { try { + await _ensureReady(); final existingIndex = _history.indexWhere( (h) => h.recipeId == item.recipeId, ); @@ -73,6 +87,7 @@ class BrowseHistoryController extends GetxController { } } await _saveHistory(); + debugPrint('✅ 浏览记录已保存: ${item.title},共 ${_history.length} 条'); } catch (e) { debugPrint('添加浏览记录失败: $e'); } @@ -80,6 +95,7 @@ class BrowseHistoryController extends GetxController { Future removeHistory(String id) async { try { + await _ensureReady(); _history.removeWhere((h) => h.id == id); await _saveHistory(); } catch (e) { @@ -89,6 +105,7 @@ class BrowseHistoryController extends GetxController { Future clearHistory() async { try { + await _ensureReady(); _history.clear(); await _saveHistory(); } catch (e) { @@ -152,6 +169,7 @@ class BrowseHistoryController extends GetxController { Future importFromJson(Map json) async { try { + await _ensureReady(); final item = BrowseHistoryModel.fromJson(json); if (!_history.any((h) => h.recipeId == item.recipeId)) { _history.add(item); diff --git a/lib/src/controllers/data/cooking_note_controller.dart b/lib/src/controllers/data/cooking_note_controller.dart index 365b78e..d75140c 100644 --- a/lib/src/controllers/data/cooking_note_controller.dart +++ b/lib/src/controllers/data/cooking_note_controller.dart @@ -1,14 +1,15 @@ -// 烹饪笔记控制器 +// 烹饪笔记控制器 // 创建时间: 2026-04-09 -// 更新时间: 2026-04-12 +// 更新时间: 2026-04-21 // 名称: cooking_note_controller.dart // 作用: 管理烹饪笔记的增删改查,支持Hive和SharedPreferences双重存储 -// 上次更新内容: 添加SharedPreferences备选存储,确保笔记正确保存 +// 上次更新内容: 修复addNote在loadNotes完成前执行导致数据覆盖的竞态条件 +import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'dart:convert'; import '../../models/data/record/cooking_note_model.dart'; import '../../services/data/storage/hive_service.dart'; @@ -20,12 +21,16 @@ class CookingNoteController extends GetxController { static const String _sharedPrefsKey = 'cooking_notes'; SharedPreferences? _prefs; + Completer? _initCompleter; + + bool get _isReady => _initCompleter != null && _initCompleter!.isCompleted; List get notes => _notes; @override void onInit() { super.onInit(); + _initCompleter = Completer(); _initPrefs(); } @@ -35,13 +40,18 @@ class CookingNoteController extends GetxController { await loadNotes(); } catch (e) { debugPrint('初始化SharedPreferences失败: $e'); + } finally { + _initCompleter?.complete(); } } - /// 加载所有烹饪笔记(优先Hive,失败则从SharedPreferences加载) + Future _ensureReady() async { + if (_isReady) return; + await _initCompleter?.future; + } + Future loadNotes() async { try { - // 先尝试从Hive加载 if (_hiveService.isInitialized) { final notes = _hiveService.getCookingNotes(); if (notes.isNotEmpty) { @@ -65,35 +75,30 @@ class CookingNoteController extends GetxController { } } - /// 添加烹饪笔记 Future addNote(CookingNoteModel note) async { try { - // 保存到Hive + await _ensureReady(); if (_hiveService.isInitialized) { await _hiveService.addCookingNote(note); debugPrint('笔记已保存到Hive: ${note.id}'); } - // 同时保存到SharedPreferences作为备份 _notes.add(note); await _saveToSharedPreferences(); debugPrint('笔记已保存到SharedPreferences: ${note.id}'); } catch (e) { debugPrint('添加烹饪笔记失败: $e'); - // 即使失败也尝试保存到内存 _notes.add(note); } } - /// 更新烹饪笔记 Future updateNote(CookingNoteModel note) async { try { - // 更新Hive + await _ensureReady(); if (_hiveService.isInitialized) { await _hiveService.updateCookingNote(note); } - // 更新SharedPreferences final index = _notes.indexWhere((n) => n.id == note.id); if (index >= 0) { _notes[index] = note; @@ -104,15 +109,13 @@ class CookingNoteController extends GetxController { } } - /// 删除烹饪笔记 Future deleteNote(String id) async { try { - // 从Hive删除 + await _ensureReady(); if (_hiveService.isInitialized) { await _hiveService.deleteCookingNote(id); } - // 从SharedPreferences删除 _notes.removeWhere((n) => n.id == id); await _saveToSharedPreferences(); } catch (e) { @@ -120,20 +123,17 @@ class CookingNoteController extends GetxController { } } - /// 获取菜谱相关的笔记 List getNotesByRecipeId(String recipeId) { return _notes.where((note) => note.recipeId == recipeId).toList(); } - /// 清空所有笔记 Future clearAllNotes() async { try { - // 清空Hive + await _ensureReady(); if (_hiveService.isInitialized) { await _hiveService.clearCookingNotes(); } - // 清空SharedPreferences _notes.clear(); await _saveToSharedPreferences(); } catch (e) { @@ -141,7 +141,6 @@ class CookingNoteController extends GetxController { } } - /// 保存到SharedPreferences Future _saveToSharedPreferences() async { try { _prefs ??= await SharedPreferences.getInstance(); @@ -153,7 +152,6 @@ class CookingNoteController extends GetxController { } } - /// 获取所有标签 List getAllTags() { final tags = {}; for (final note in _notes) { @@ -162,7 +160,6 @@ class CookingNoteController extends GetxController { return tags.toList()..sort(); } - /// 根据标签筛选笔记 List getNotesByTag(String tag) { return _notes.where((note) => note.tags.contains(tag)).toList(); } @@ -209,11 +206,12 @@ class CookingNoteController extends GetxController { return buffer.toString(); } - void importFromJson(Map json) { + Future importFromJson(Map json) async { try { + await _ensureReady(); final note = CookingNoteModel.fromJson(json); if (!_notes.any((n) => n.id == note.id)) { - addNote(note); + await addNote(note); } } catch (e) { debugPrint('CookingNoteController: importFromJson failed: $e'); diff --git a/lib/src/controllers/data/share_record_controller.dart b/lib/src/controllers/data/share_record_controller.dart index a6ecc3b..2294f76 100644 --- a/lib/src/controllers/data/share_record_controller.dart +++ b/lib/src/controllers/data/share_record_controller.dart @@ -1,10 +1,12 @@ -// 2026-04-19 | share_record_controller.dart | 分享记录控制器 | 管理菜谱分享历史 +// 2026-04-19 | share_record_controller.dart | 分享记录控制器 | 管理菜谱分享历史 // 2026-04-19 | 初始创建,使用 SharedPreferences JSON 持久化 +// 2026-04-21 | 修复addRecord在loadRecords完成前执行导致数据覆盖的竞态条件 +import 'dart:async'; +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'dart:convert'; import '../../models/data/record/share_record_model.dart'; class ShareRecordController extends GetxController { @@ -49,10 +51,14 @@ class ShareRecordController extends GetxController { } SharedPreferences? _prefs; + Completer? _initCompleter; + + bool get _isReady => _initCompleter != null && _initCompleter!.isCompleted; @override void onInit() { super.onInit(); + _initCompleter = Completer(); _initPrefs(); } @@ -63,9 +69,16 @@ class ShareRecordController extends GetxController { debugPrint('分享记录初始化完成,共 ${_records.length} 条记录'); } catch (e) { debugPrint('初始化SharedPreferences失败: $e'); + } finally { + _initCompleter?.complete(); } } + Future _ensureReady() async { + if (_isReady) return; + await _initCompleter?.future; + } + Future loadRecords() async { try { _prefs ??= await SharedPreferences.getInstance(); @@ -85,6 +98,7 @@ class ShareRecordController extends GetxController { Future addRecord(ShareRecordModel record) async { try { + await _ensureReady(); _records.insert(0, record); if (_records.length > _maxRecordCount) { _records.removeRange(_maxRecordCount, _records.length); @@ -98,6 +112,7 @@ class ShareRecordController extends GetxController { Future removeRecord(String id) async { try { + await _ensureReady(); _records.removeWhere((r) => r.id == id); await _saveRecords(); } catch (e) { @@ -107,6 +122,7 @@ class ShareRecordController extends GetxController { Future clearRecords() async { try { + await _ensureReady(); _records.clear(); await _saveRecords(); } catch (e) { diff --git a/lib/src/l10n/app_en.arb b/lib/src/l10n/app_en.arb index c5375b9..7f56538 100644 --- a/lib/src/l10n/app_en.arb +++ b/lib/src/l10n/app_en.arb @@ -1,8 +1,8 @@ { "@@locale": "en", - "appTitle": "Mom's Kitchen", - "welcomeMessage": "Welcome to Mom's Kitchen!", + "appTitle": "Cute Kitchen", + "welcomeMessage": "Welcome to Cute Kitchen!", "appDescription": "Your favorite food delivery app", "getStarted": "Get Started", @@ -33,7 +33,7 @@ "reload": "Reload", "searchAgain": "Search Again", - "homeTitle": "Mom's Kitchen", + "homeTitle": "Cute Kitchen", "searchPlaceholder": "Search recipes, ingredients...", "loadingRecipes": "Loading recipes...", "loadFailed": "Load Failed", diff --git a/lib/src/l10n/app_localizations.dart b/lib/src/l10n/app_localizations.dart index 8d564fc..c5c1bd2 100644 --- a/lib/src/l10n/app_localizations.dart +++ b/lib/src/l10n/app_localizations.dart @@ -102,13 +102,13 @@ abstract class AppLocalizations { /// No description provided for @appTitle. /// /// In en, this message translates to: - /// **'Mom\'s Kitchen'** + /// **'Cute Kitchen'** String get appTitle; /// No description provided for @welcomeMessage. /// /// In en, this message translates to: - /// **'Welcome to Mom\'s Kitchen!'** + /// **'Welcome to Cute Kitchen!'** String get welcomeMessage; /// No description provided for @appDescription. @@ -276,7 +276,7 @@ abstract class AppLocalizations { /// No description provided for @homeTitle. /// /// In en, this message translates to: - /// **'Mom\'s Kitchen'** + /// **'Cute Kitchen'** String get homeTitle; /// No description provided for @searchPlaceholder. diff --git a/lib/src/l10n/app_localizations_en.dart b/lib/src/l10n/app_localizations_en.dart index 551c8c1..b99b1ab 100644 --- a/lib/src/l10n/app_localizations_en.dart +++ b/lib/src/l10n/app_localizations_en.dart @@ -9,10 +9,10 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get appTitle => 'Mom\'s Kitchen'; + String get appTitle => 'Cute Kitchen'; @override - String get welcomeMessage => 'Welcome to Mom\'s Kitchen!'; + String get welcomeMessage => 'Welcome to Cute Kitchen!'; @override String get appDescription => 'Your favorite food delivery app'; @@ -97,7 +97,7 @@ class AppLocalizationsEn extends AppLocalizations { String get searchAgain => 'Search Again'; @override - String get homeTitle => 'Mom\'s Kitchen'; + String get homeTitle => 'Cute Kitchen'; @override String get searchPlaceholder => 'Search recipes, ingredients...'; diff --git a/lib/src/models/feed/feed_item_model.dart b/lib/src/models/feed/feed_item_model.dart index a57ef5e..8e089a9 100644 --- a/lib/src/models/feed/feed_item_model.dart +++ b/lib/src/models/feed/feed_item_model.dart @@ -98,7 +98,7 @@ class FeedItemModel { categoryId: json['category_id'] as int? ?? json['cate_id'] as int?, tags: _parseTags(json['tags']), statistics: _parseStatistics(json['statistics']), - createdAt: json['created_at'] as String? ?? json['post_time'] as String?, + createdAt: json['created_at'] as String? ?? json['createdAt'] as String? ?? json['post_time'] as String?, feedType: json['feed_type'] as String?, mdhwScore: _toDouble(json['mdhw_score']), favoriteType: FavoriteType.fromString(json['favorite_type'] as String?), diff --git a/lib/src/models/feedback_model.dart b/lib/src/models/feedback_model.dart new file mode 100644 index 0000000..a288b41 --- /dev/null +++ b/lib/src/models/feedback_model.dart @@ -0,0 +1,298 @@ +// 2026-04-21 | feedback_model.dart | 反馈系统数据模型 | 定义反馈会话、消息和设备信息的数据结构 +// 2026-04-21 | 初始创建,支持5轮对话、设备信息收集、邮件发送 + +/// 反馈类型枚举 +enum FeedbackType { + bug('\u{1F41B}', 'Bug 反馈'), + feature('\u{2728}', '功能建议'), + experience('\u{1F44D}', '体验优化'), + other('\u{1F4AC}', '其他'); + + final String emoji; + final String label; + const FeedbackType(this.emoji, this.label); +} + +/// 反馈状态枚举(状态机) +enum FeedbackState { + idle, // 初始状态:显示欢迎语+类型选择 + selecting, // 用户正在选择反馈类型 + chatting_1, // 第1轮用户输入 + chatting_2, // 第2轮 + chatting_3, // 第3轮 + chatting_4, // 第4轮(最后一轮) + completed, // 对话完成:显示发送按钮+邮箱输入+预览 + sending, // 正在发送中... + sentSuccess, // 发送成功:显示成功提示+自动回复说明 + sentFailed, // 发送失败:显示重试按钮 +} + +/// 反馈会话状态枚举(用于存储) +enum FeedbackSessionStatus { + draft, // 草稿(对话中) + sent, // 已发送 + failed, // 发送失败 +} + +/// 设备信息模型 +class DeviceInfo { + final String uuid; // 设备UUID + final String ip; // IP地址 + final String appVersion; // 应用版本号 + final String platform; // 平台(iOS/Android/Windows) + final String osVersion; // 系统版本 + final String logs; // 相关日志(最近20条) + + const DeviceInfo({ + required this.uuid, + required this.ip, + required this.appVersion, + required this.platform, + required this.osVersion, + required this.logs, + }); + + /// 转换为Map(用于JSON序列化) + Map toMap() { + return { + 'uuid': uuid, + 'ip': ip, + 'appVersion': appVersion, + 'platform': platform, + 'osVersion': osVersion, + 'logs': logs, + }; + } + + /// 从Map创建(用于JSON反序列化) + factory DeviceInfo.fromMap(Map map) { + return DeviceInfo( + uuid: map['uuid'] ?? '', + ip: map['ip'] ?? '', + appVersion: map['appVersion'] ?? '', + platform: map['platform'] ?? '', + osVersion: map['osVersion'] ?? '', + logs: map['logs'] ?? '', + ); + } +} + +/// 反馈消息模型 +class FeedbackMessage { + final String text; + final bool isUser; + final FeedbackType? feedbackType; + final DateTime timestamp; + + FeedbackMessage({ + required this.text, + required this.isUser, + this.feedbackType, + DateTime? timestamp, + }) : timestamp = timestamp ?? DateTime.now(); + + /// 转换为Map(用于JSON序列化) + Map toMap() { + return { + 'text': text, + 'isUser': isUser, + 'feedbackType': feedbackType?.name, + 'timestamp': timestamp.toIso8601String(), + }; + } + + /// 从Map创建(用于JSON反序列化) + factory FeedbackMessage.fromMap(Map map) { + return FeedbackMessage( + text: map['text'] ?? '', + isUser: map['isUser'] ?? false, + feedbackType: map['feedbackType'] != null + ? FeedbackType.values.firstWhere( + (e) => e.name == map['feedbackType'], + orElse: () => FeedbackType.other, + ) + : null, + timestamp: map['timestamp'] != null + ? DateTime.parse(map['timestamp']) + : DateTime.now(), + ); + } +} + +/// 反馈会话模型(完整的一次反馈流程) +class FeedbackSession { + final String id; // 唯一ID(时间戳格式:20260421_1234567890) + final FeedbackType type; // 反馈类型 + final List messages; // 消息列表 + final DateTime createdAt; // 创建时间 + final DateTime? completedAt; // 完成时间 + final String? userEmail; // 用户邮箱(选填) + final DeviceInfo deviceInfo; // 设备信息 + final FeedbackSessionStatus status; // 会话状态 + + const FeedbackSession({ + required this.id, + required this.type, + required this.messages, + required this.createdAt, + this.completedAt, + this.userEmail, + required this.deviceInfo, + this.status = FeedbackSessionStatus.draft, + }); + + /// 创建新会话(工厂方法) + factory FeedbackSession.create({ + required FeedbackType type, + required DeviceInfo deviceInfo, + }) { + return FeedbackSession( + id: _generateId(), + type: type, + messages: [], + createdAt: DateTime.now(), + deviceInfo: deviceInfo, + status: FeedbackSessionStatus.draft, + ); + } + + /// 生成唯一ID + static String _generateId() { + final now = DateTime.now(); + return '${now.year}${now.month.toString().padLeft(2, '0')}' + '${now.day.toString().padLeft(2, '0')}_' + '${now.millisecondsSinceEpoch}'; + } + + /// 获取用户消息列表 + List get userMessages => + messages.where((m) => m.isUser).toList(); + + /// 获取Bot消息列表 + List get botMessages => + messages.where((m) => !m.isUser).toList(); + + /// 获取当前聊天轮次(从1开始) + int get currentRound => userMessages.length; + + /// 是否可以继续输入(最多4轮用户输入) + bool get canContinueChat => currentRound < 4; + + /// 是否已完成所有轮次 + bool get isCompleted => + currentRound >= 4 && status == FeedbackSessionStatus.draft; + + /// 添加消息 + FeedbackSession addMessage(FeedbackMessage message) { + return FeedbackSession( + id: id, + type: type, + messages: [...messages, message], + createdAt: createdAt, + completedAt: completedAt, + userEmail: userEmail, + deviceInfo: deviceInfo, + status: status, + ); + } + + /// 标记为完成 + FeedbackSession markAsCompleted() { + return FeedbackSession( + id: id, + type: type, + messages: messages, + createdAt: createdAt, + completedAt: DateTime.now(), + userEmail: userEmail, + deviceInfo: deviceInfo, + status: FeedbackSessionStatus.draft, // 完成但未发送 + ); + } + + /// 设置用户邮箱 + FeedbackSession withUserEmail(String? email) { + return FeedbackSession( + id: id, + type: type, + messages: messages, + createdAt: createdAt, + completedAt: completedAt, + userEmail: email, + deviceInfo: deviceInfo, + status: status, + ); + } + + /// 标记为已发送 + FeedbackSession markAsSent() { + return FeedbackSession( + id: id, + type: type, + messages: messages, + createdAt: createdAt, + completedAt: completedAt, + userEmail: userEmail, + deviceInfo: deviceInfo, + status: FeedbackSessionStatus.sent, + ); + } + + /// 标记为发送失败 + FeedbackSession markAsFailed() { + return FeedbackSession( + id: id, + type: type, + messages: messages, + createdAt: createdAt, + completedAt: completedAt, + userEmail: userEmail, + deviceInfo: deviceInfo, + status: FeedbackSessionStatus.failed, + ); + } + + /// 转换为Map(用于JSON序列化) + Map toMap() { + return { + 'id': id, + 'type': type.name, + 'messages': messages.map((m) => m.toMap()).toList(), + 'createdAt': createdAt.toIso8601String(), + 'completedAt': completedAt?.toIso8601String(), + 'userEmail': userEmail, + 'deviceInfo': deviceInfo.toMap(), + 'status': status.name, + }; + } + + /// 从Map创建(用于JSON反序列化) + factory FeedbackSession.fromMap(Map map) { + return FeedbackSession( + id: map['id'] ?? '', + type: FeedbackType.values.firstWhere( + (e) => e.name == map['type'], + orElse: () => FeedbackType.other, + ), + messages: + (map['messages'] as List?) + ?.map((m) => FeedbackMessage.fromMap(m as Map)) + .toList() ?? + [], + createdAt: map['createdAt'] != null + ? DateTime.parse(map['createdAt']) + : DateTime.now(), + completedAt: map['completedAt'] != null + ? DateTime.parse(map['completedAt']) + : null, + userEmail: map['userEmail'], + deviceInfo: DeviceInfo.fromMap( + map['deviceInfo'] as Map? ?? {}, + ), + status: FeedbackSessionStatus.values.firstWhere( + (e) => e.name == map['status'], + orElse: () => FeedbackSessionStatus.draft, + ), + ); + } +} diff --git a/lib/src/pages/discover/discover_page.dart b/lib/src/pages/discover/discover_page.dart index d39278f..8d9ed1b 100644 --- a/lib/src/pages/discover/discover_page.dart +++ b/lib/src/pages/discover/discover_page.dart @@ -7,6 +7,10 @@ * 更新: 2026-04-16 新增下拉进入工具中心功能,移除顶部4个固定按钮至收藏页 * 更新: 2026-04-16 代码拆分,将工具面板和分区内容提取为独立组件 * 更新: 2026-04-16 工具中心改为从顶部滑入页面导航,支持系统返回键,动画可打断 + * 更新: 2026-04-20 修复页面下拉无法触发工具中心问题,改用 GestureDetector 监听垂直拖拽手势,解决 PageView 手势冲突 + * 更新: 2026-04-20 新增 iOS 风格下拉动画反馈(进度指示器+图标旋转+弹性回弹),提升用户体验 + * 更新: 2026-04-20 修复 LateInitializationError,将 AnimationController 改为可空类型增强安全性 + * 更新: 2026-04-20 实现全局下拉劫持机制,拦截所有 tab 内部 ScrollView 的过滚动事件统一触发工具中心 */ import 'dart:ui'; @@ -34,7 +38,8 @@ class DiscoverPage extends StatefulWidget { State createState() => _DiscoverPageState(); } -class _DiscoverPageState extends State { +class _DiscoverPageState extends State + with SingleTickerProviderStateMixin { int _segmentIndex = 0; int _recommendTypeIndex = 0; int _recommendSubIndex = 0; @@ -50,6 +55,13 @@ class _DiscoverPageState extends State { late final PageController _pageController; + /// 下拉触发工具中心相关状态 + double _dragOffset = 0.0; + bool _isDraggingDown = false; + bool _hasTriggeredToolsCenter = false; + bool _showPullIndicator = false; + AnimationController? _pullAnimationController; + /// 搜索相关 final TextEditingController _searchController = TextEditingController(); final FocusNode _searchFocusNode = FocusNode(); @@ -65,6 +77,12 @@ class _DiscoverPageState extends State { // 搜索框响应式搜索监听 _searchController.addListener(_onSearchChanged); + + // 下拉动画控制器 + _pullAnimationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 250), + ); } void _onSearchChanged() { @@ -91,6 +109,7 @@ class _DiscoverPageState extends State { _searchController.dispose(); _searchFocusNode.dispose(); _pageController.dispose(); + _pullAnimationController?.dispose(); super.dispose(); } @@ -165,6 +184,277 @@ class _DiscoverPageState extends State { } } + /// 处理滚动通知,检测顶部下拉手势以触发工具中心 + void _handleVerticalDragStart(DragStartDetails details) { + _isDraggingDown = true; + _dragOffset = 0.0; + _hasTriggeredToolsCenter = false; + + if (!_showPullIndicator) { + setState(() => _showPullIndicator = true); + } + + _pullAnimationController?.reset(); + } + + void _handleVerticalDragUpdate(DragUpdateDetails details) { + if (!_isDraggingDown || _pullAnimationController == null) return; + + final delta = details.delta.dy; + + if (delta > 0) { + _dragOffset += delta; + + final progress = (_dragOffset / 120.0).clamp(0.0, 1.0); + + if (_pullAnimationController!.value != progress) { + _pullAnimationController!.value = progress; + } + + if (_dragOffset > 100 && !_hasTriggeredToolsCenter) { + _hasTriggeredToolsCenter = true; + HapticFeedback.heavyImpact(); + } + } else { + _dragOffset = (_dragOffset + delta).clamp(0.0, _dragOffset); + + final progress = (_dragOffset / 120.0).clamp(0.0, 1.0); + if (_pullAnimationController!.value != progress) { + _pullAnimationController!.value = progress; + } + + if (_dragOffset < 80 && _hasTriggeredToolsCenter) { + _hasTriggeredToolsCenter = false; + } + } + } + + void _handleVerticalDragEnd(DragEndDetails details) async { + _isDraggingDown = false; + + if (_hasTriggeredToolsCenter && + mounted && + _pullAnimationController != null) { + await _pullAnimationController!.forward(from: 1.0); + + Future.delayed(const Duration(milliseconds: 150), () { + if (mounted) { + _openToolsCenter(); + + Future.delayed(const Duration(milliseconds: 400), () { + if (mounted) { + _resetPullState(); + } + }); + } + }); + } else { + await _pullAnimationController?.reverse(); + _resetPullState(); + } + } + + void _resetPullState() { + if (mounted) { + setState(() { + _showPullIndicator = false; + _dragOffset = 0.0; + _hasTriggeredToolsCenter = false; + }); + } + } + + bool _handleScrollNotification(ScrollNotification notification) { + if (notification is OverscrollNotification) { + final overscroll = notification.overscroll; + + if (overscroll < 0 && !_isDraggingDown) { + _isDraggingDown = true; + _dragOffset = overscroll.abs(); + _hasTriggeredToolsCenter = false; + + if (!_showPullIndicator && mounted) { + setState(() => _showPullIndicator = true); + } + + if (_pullAnimationController != null) { + final progress = (_dragOffset / 120.0).clamp(0.0, 1.0); + _pullAnimationController!.value = progress; + } + + if (_dragOffset > 100 && !_hasTriggeredToolsCenter) { + _hasTriggeredToolsCenter = true; + HapticFeedback.heavyImpact(); + } + } else if (_isDraggingDown && overscroll < 0) { + _dragOffset = (_dragOffset + overscroll.abs()).clamp( + 0.0, + _dragOffset + overscroll.abs(), + ); + + if (_pullAnimationController != null) { + final progress = (_dragOffset / 120.0).clamp(0.0, 1.0); + _pullAnimationController!.value = progress; + } + + if (_dragOffset > 100 && !_hasTriggeredToolsCenter) { + _hasTriggeredToolsCenter = true; + HapticFeedback.heavyImpact(); + } + } + + return true; + } + + if (notification is ScrollUpdateNotification) { + if (notification.metrics.pixels <= 0) { + if (!_isDraggingDown) { + _hasTriggeredToolsCenter = false; + } + } + } + + if (notification is ScrollEndNotification) { + if (_isDraggingDown) { + _handleOverscrollEnd(); + } + } + + return false; + } + + void _handleOverscrollEnd() async { + _isDraggingDown = false; + + if (_hasTriggeredToolsCenter && + mounted && + _pullAnimationController != null) { + await _pullAnimationController!.forward(from: 1.0); + + Future.delayed(const Duration(milliseconds: 150), () { + if (mounted) { + _openToolsCenter(); + + Future.delayed(const Duration(milliseconds: 400), () { + if (mounted) { + _resetPullState(); + } + }); + } + }); + } else { + await _pullAnimationController?.reverse(); + _resetPullState(); + } + } + + Widget _buildPullDownIndicator(bool isDark) { + if (_pullAnimationController == null) { + return const SizedBox.shrink(); + } + + return AnimatedBuilder( + animation: _pullAnimationController!, + builder: (context, child) { + final progress = _pullAnimationController!.value; + final opacity = progress.clamp(0.0, 1.0); + final iconScale = 0.8 + (progress * 0.4); + final iconRotation = progress * 3.14159; + final shouldTrigger = _hasTriggeredToolsCenter; + + return Opacity( + opacity: opacity, + child: Transform.translate( + offset: Offset(0, -60 * (1 - progress)), + child: Container( + height: 60, + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.glass.withValues(alpha: 0.9) + : DesignTokens.card.withValues(alpha: 0.95), + borderRadius: const BorderRadius.vertical( + bottom: Radius.circular(20), + ), + boxShadow: [ + BoxShadow( + color: isDark + ? CupertinoColors.black.withValues(alpha: 0.3 * opacity) + : CupertinoColors.systemGrey5.withValues( + alpha: 0.15 * opacity, + ), + blurRadius: 12 * opacity, + offset: Offset(0, 4 * opacity), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Transform.rotate( + angle: iconRotation, + child: Transform.scale( + scale: iconScale, + child: Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: shouldTrigger + ? DesignTokens.dynamicPrimary + : DesignTokens.dynamicPrimary.withValues( + alpha: 0.15, + ), + borderRadius: BorderRadius.circular(10), + boxShadow: shouldTrigger + ? [ + BoxShadow( + color: DesignTokens.dynamicPrimary + .withValues(alpha: 0.4), + blurRadius: 8, + spreadRadius: 2, + ), + ] + : null, + ), + child: Center( + child: Text('🛠️', style: TextStyle(fontSize: 18)), + ), + ), + ), + ), + const SizedBox(width: DesignTokens.space3), + AnimatedDefaultTextStyle( + duration: const Duration(milliseconds: 200), + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: shouldTrigger + ? FontWeight.w700 + : FontWeight.w500, + color: shouldTrigger + ? DesignTokens.dynamicPrimary + : (isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1), + letterSpacing: 0.5, + ), + child: Text(shouldTrigger ? '释放打开工具中心' : '下拉打开工具中心'), + ), + if (shouldTrigger) ...[ + const SizedBox(width: DesignTokens.space2), + Icon( + CupertinoIcons.arrow_up, + size: 16, + color: DesignTokens.dynamicPrimary, + ), + ], + ], + ), + ), + ), + ); + }, + ); + } + @override Widget build(BuildContext context) { final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; @@ -188,36 +478,56 @@ class _DiscoverPageState extends State { children: [ if (_searchQuery.isNotEmpty) _buildSearchResults(isDark), Expanded( - child: PageView( - controller: _pageController, - onPageChanged: (i) { - setState(() => _segmentIndex = i); - }, - children: List.generate(3, (index) { - return SingleChildScrollView( - padding: const EdgeInsets.only( - bottom: DesignTokens.space6, + child: Stack( + children: [ + GestureDetector( + behavior: HitTestBehavior.translucent, + onVerticalDragStart: _handleVerticalDragStart, + onVerticalDragUpdate: _handleVerticalDragUpdate, + onVerticalDragEnd: _handleVerticalDragEnd, + child: NotificationListener( + onNotification: _handleScrollNotification, + child: PageView( + controller: _pageController, + onPageChanged: (i) { + setState(() => _segmentIndex = i); + }, + children: List.generate(3, (index) { + return SingleChildScrollView( + padding: const EdgeInsets.only( + bottom: DesignTokens.space6, + ), + child: DiscoverSectionsWidget( + isDark: isDark, + segmentIndex: index, + recommendTypeIndex: _recommendTypeIndex, + recommendSubIndex: _recommendSubIndex, + hotController: _hotController, + topCategories: _topCategories, + ingredientCategories: _ingredientCategories, + tasteTags: _tasteTags, + cookingTags: _cookingTags, + isLoadingCategories: _isLoadingCategories, + onRecommendSubIndexChanged: (i) { + setState(() => _recommendSubIndex = i); + }, + onRecommendTypeIndexChanged: (i) { + setState(() => _recommendTypeIndex = i); + }, + ), + ); + }), + ), ), - child: DiscoverSectionsWidget( - isDark: isDark, - segmentIndex: index, - recommendTypeIndex: _recommendTypeIndex, - recommendSubIndex: _recommendSubIndex, - hotController: _hotController, - topCategories: _topCategories, - ingredientCategories: _ingredientCategories, - tasteTags: _tasteTags, - cookingTags: _cookingTags, - isLoadingCategories: _isLoadingCategories, - onRecommendSubIndexChanged: (i) { - setState(() => _recommendSubIndex = i); - }, - onRecommendTypeIndexChanged: (i) { - setState(() => _recommendTypeIndex = i); - }, + ), + if (_showPullIndicator) + Positioned( + top: 0, + left: 0, + right: 0, + child: _buildPullDownIndicator(isDark), ), - ); - }), + ], ), ), ], diff --git a/lib/src/pages/home/home_page.dart b/lib/src/pages/home/home_page.dart index d4ea93f..c093f2e 100644 --- a/lib/src/pages/home/home_page.dart +++ b/lib/src/pages/home/home_page.dart @@ -13,6 +13,7 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/app_routes.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; import 'package:mom_kitchen/src/repositories/recipe_repository.dart'; import 'package:mom_kitchen/src/repositories/discover_repository.dart'; @@ -85,6 +86,8 @@ class _HomePageState extends State { DateTime? _lastLoadMoreTime; static const Duration _loadMoreThrottle = Duration(milliseconds: 500); final bool _scrollNotificationEnabled = true; + // 当由代码触发回到顶部时,短时间内抑制下拉刷新触发 + bool _suppressRefreshDuringProgrammaticScroll = false; @override void initState() { @@ -104,11 +107,21 @@ class _HomePageState extends State { void _scrollToTop() { if (_mainScrollController.hasClients) { - _mainScrollController.animateTo( + // 抑制程序化滚动触发的下拉刷新(避免误触发 onRefresh) + _suppressRefreshDuringProgrammaticScroll = true; + _mainScrollController + .animateTo( 0, duration: const Duration(milliseconds: 400), curve: Curves.easeOutCubic, - ); + ) + .whenComplete(() { + // 给系统一个短暂时窗来完成滚动手势,之后恢复刷新能力 + Future.delayed(const Duration(milliseconds: 300), () { + if (!mounted) return; + _suppressRefreshDuringProgrammaticScroll = false; + }); + }); } } @@ -524,11 +537,9 @@ class _HomePageState extends State { return HomeAppBar( isSubBarVisible: isSubBarVisible, onNotificationTap: () { - ToastService.show(message: '通知功能开发中 🔔'); - }, - onSettingsTap: () { - _scrollToTop(); + Get.toNamed(AppRoutes.notice); }, + onSettingsTap: _scrollToTop, ); }, ), @@ -788,11 +799,18 @@ class _HomePageState extends State { final List sliverList = [ CupertinoSliverRefreshControl( onRefresh: () async { + // 如果正在由代码触发回到顶部,忽略本次下拉刷新。 + if (_suppressRefreshDuringProgrammaticScroll) { + await Future.delayed(const Duration(milliseconds: 200)); + return; + } + if (_pendingDiscoverData != null) { _applyPendingDiscoverIfAny(); await _loadRecipes(refresh: true); return; } + await Future.wait([ _loadRecipes(refresh: true), _loadDiscover(refresh: true), diff --git a/lib/src/pages/profile/data/food_timeline_page.dart b/lib/src/pages/profile/data/food_timeline_page.dart new file mode 100644 index 0000000..a7cdea1 --- /dev/null +++ b/lib/src/pages/profile/data/food_timeline_page.dart @@ -0,0 +1,1187 @@ +/* + * 文件: food_timeline_page.dart + * 名称: 美食年轮页面 + * 作用: 可视化美食旅程档案,聚合浏览/收藏/笔记/评分/饮食记录数据 + * 创建: 2026-04-21 + * 更新: 2026-04-21 初始创建,包含总览/味觉DNA/里程碑/月度足迹/菜系探索度 + */ + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart' show Colors; +import 'package:get/get.dart'; +import 'package:mom_kitchen/src/config/design_tokens.dart'; +import 'package:mom_kitchen/src/controllers/data/browse_history_controller.dart'; +import 'package:mom_kitchen/src/controllers/data/cooking_note_controller.dart'; +import 'package:mom_kitchen/src/controllers/data/rating_records_controller.dart'; +import 'package:mom_kitchen/src/controllers/user/favorites_controller.dart'; +import 'package:mom_kitchen/src/services/core/taste_preference_service.dart'; + +class FoodTimelinePage extends StatelessWidget { + const FoodTimelinePage({super.key}); + + @override + Widget build(BuildContext context) { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + + return CupertinoPageScaffold( + backgroundColor: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + navigationBar: CupertinoNavigationBar( + middle: Text( + '美食年轮(Beta)', + style: TextStyle( + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + ), + ), + leading: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () => Get.back(), + child: Icon(CupertinoIcons.back, color: DesignTokens.dynamicPrimary), + ), + backgroundColor: + (isDark ? DarkDesignTokens.background : DesignTokens.background) + .withValues(alpha: 0.9), + border: null, + ), + child: SafeArea( + child: Obx(() { + _triggerReactiveAccess(); + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space2, + ), + child: Column( + children: [ + _buildHeroSection(isDark), + const SizedBox(height: DesignTokens.space4), + _buildStatsGrid(isDark), + const SizedBox(height: DesignTokens.space4), + _buildTasteDNA(isDark), + const SizedBox(height: DesignTokens.space4), + _buildMilestones(isDark), + const SizedBox(height: DesignTokens.space4), + _buildMonthlyFootprint(isDark), + const SizedBox(height: DesignTokens.space4), + _buildCuisineExplore(isDark), + const SizedBox(height: DesignTokens.space6), + ], + ), + ); + }), + ), + ); + } + + Widget _buildHeroSection(bool isDark) { + final browseCount = _getBrowseCount(); + final days = _getUsageDays(); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space5), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + DesignTokens.dynamicPrimary.withValues(alpha: 0.08), + DesignTokens.dynamicPrimary.withValues(alpha: 0.02), + ], + ), + borderRadius: DesignTokens.borderRadiusLg, + border: Border.all( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.12), + ), + ), + child: Column( + children: [ + SizedBox( + width: 120, + height: 120, + child: CustomPaint( + painter: _RingPainter( + rings: _calculateRings(browseCount), + primaryColor: DesignTokens.dynamicPrimary, + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '$days', + style: TextStyle( + fontSize: 32, + fontWeight: FontWeight.w800, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + height: 1.0, + ), + ), + const SizedBox(height: 2), + Text( + '天', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + ), + ), + const SizedBox(height: DesignTokens.space3), + Text( + '每一圈,都是味道的记忆', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space1), + Text( + '你的美食旅程,从这里开始记录', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ], + ), + ); + } + + Widget _buildStatsGrid(bool isDark) { + final browseCount = _getBrowseCount(); + final favCount = _getFavoritesCount(); + final noteCount = _getNotesCount(); + final ratingCount = _getRatingCount(); + + final items = [ + _StatItem('📖', '浏览菜谱', browseCount, DesignTokens.dynamicPrimary), + _StatItem('❤️', '收藏菜谱', favCount, DesignTokens.red), + _StatItem('📝', '笔记', noteCount, DesignTokens.orange), + _StatItem('⭐', '评分', ratingCount, DesignTokens.gold), + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionTitle('📊 数据总览', isDark), + const SizedBox(height: DesignTokens.space2), + Row( + children: items.map((item) { + return Expanded( + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: DesignTokens.space1, + ), + padding: const EdgeInsets.symmetric( + vertical: DesignTokens.space3, + horizontal: DesignTokens.space2, + ), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusMd, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + children: [ + Text(item.emoji, style: const TextStyle(fontSize: 20)), + const SizedBox(height: DesignTokens.space1), + Text( + '${item.count}', + style: TextStyle( + fontSize: DesignTokens.fontXxl, + fontWeight: FontWeight.w800, + color: item.color, + height: 1.1, + ), + ), + const SizedBox(height: 2), + Text( + item.label, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + ); + }).toList(), + ), + ], + ); + } + + Widget _buildTasteDNA(bool isDark) { + final tasteService = _getTasteService(); + final spiceVal = _spiceToValue(tasteService?.spiceLevel.value); + final sweetVal = tasteService?.sweetness.value ?? 0.5; + final saltyVal = tasteService?.saltiness.value ?? 0.5; + final sourVal = tasteService?.sourness.value ?? 0.3; + final umamiVal = _inferUmami(); + final aromaVal = _inferAroma(); + + final personality = _getTastePersonality( + spiceVal, + sweetVal, + saltyVal, + sourVal, + ); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('🧬', style: TextStyle(fontSize: 18)), + const SizedBox(width: DesignTokens.space2), + Text( + '味觉DNA', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space3, + vertical: DesignTokens.space1, + ), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusFull, + ), + child: Text( + personality, + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w600, + color: DesignTokens.dynamicPrimary, + ), + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space4), + SizedBox( + height: 220, + child: RadarChart( + RadarChartData( + radarBackgroundColor: Colors.transparent, + radarBorderData: BorderSide( + color: (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.15), + ), + tickCount: 4, + gridBorderData: BorderSide( + color: (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.1), + ), + getTitle: (index, _) { + const titles = [ + '🌶 辣', + '🍬 甜', + '🧂 咸', + '🍋 酸', + '🍄 鲜', + '🌿 香', + ]; + if (index < titles.length) { + return RadarChartTitle( + text: titles[index], + positionPercentageOffset: 0.08, + ); + } + return const RadarChartTitle(text: ''); + }, + dataSets: [ + RadarDataSet( + fillColor: DesignTokens.dynamicPrimary.withValues( + alpha: 0.15, + ), + borderColor: DesignTokens.dynamicPrimary.withValues( + alpha: 0.8, + ), + borderWidth: 2, + entryRadius: 3, + dataEntries: [ + RadarEntry(value: spiceVal), + RadarEntry(value: sweetVal), + RadarEntry(value: saltyVal), + RadarEntry(value: sourVal), + RadarEntry(value: umamiVal), + RadarEntry(value: aromaVal), + ], + ), + ], + ), + duration: DesignTokens.durationSlow, + ), + ), + const SizedBox(height: DesignTokens.space2), + Center( + child: Text( + '基于你的口味偏好设置和浏览数据生成', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + ), + ], + ), + ); + } + + Widget _buildMilestones(bool isDark) { + final milestones = _calculateMilestones(); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('🏆', style: TextStyle(fontSize: 18)), + const SizedBox(width: DesignTokens.space2), + Text( + '美食里程碑', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + Text( + '${milestones.where((m) => m.unlocked).length}/${milestones.length}', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: DesignTokens.dynamicPrimary, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space3), + ...milestones.map((m) => _buildMilestoneItem(m, isDark)), + ], + ), + ); + } + + Widget _buildMilestoneItem(_Milestone milestone, bool isDark) { + return Padding( + padding: const EdgeInsets.only(bottom: DesignTokens.space2), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: + (milestone.unlocked + ? DesignTokens.dynamicPrimary + : (isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3)) + .withValues(alpha: 0.12), + borderRadius: DesignTokens.borderRadiusMd, + ), + child: Center( + child: Text( + milestone.unlocked ? milestone.emoji : '🔒', + style: const TextStyle(fontSize: 16), + ), + ), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + milestone.title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: milestone.unlocked + ? (isDark ? DarkDesignTokens.text1 : DesignTokens.text1) + : (isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3), + ), + ), + if (milestone.unlocked && milestone.date != null) + Text( + milestone.date!, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + if (milestone.unlocked) + Icon( + CupertinoIcons.checkmark_circle_fill, + size: 20, + color: DesignTokens.green, + ), + ], + ), + ); + } + + Widget _buildMonthlyFootprint(bool isDark) { + final monthlyData = _getMonthlyData(); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('📅', style: TextStyle(fontSize: 18)), + const SizedBox(width: DesignTokens.space2), + Text( + '月度足迹', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space3), + if (monthlyData.isEmpty) + Center( + child: Padding( + padding: const EdgeInsets.all(DesignTokens.space5), + child: Column( + children: [ + const Text('🌱', style: TextStyle(fontSize: 32)), + const SizedBox(height: DesignTokens.space2), + Text( + '开始浏览菜谱,你的足迹将在这里呈现', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + ) + else + ...monthlyData.map((m) => _buildMonthCard(m, isDark)), + ], + ), + ); + } + + Widget _buildMonthCard(_MonthData month, bool isDark) { + return Container( + margin: const EdgeInsets.only(bottom: DesignTokens.space2), + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.04), + borderRadius: DesignTokens.borderRadiusMd, + border: Border.all( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.08), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + month.label, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w700, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + if (month.topCategory != null) + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: DesignTokens.orange.withValues(alpha: 0.12), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Text( + '🔥 ${month.topCategory}', + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w500, + color: DesignTokens.orange, + ), + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + Row( + children: [ + _buildMonthTag('📖 ${month.browseCount}', isDark), + const SizedBox(width: DesignTokens.space2), + _buildMonthTag('❤️ ${month.favCount}', isDark), + const SizedBox(width: DesignTokens.space2), + _buildMonthTag('📝 ${month.noteCount}', isDark), + const SizedBox(width: DesignTokens.space2), + _buildMonthTag('⭐ ${month.ratingCount}', isDark), + ], + ), + ], + ), + ); + } + + Widget _buildMonthTag(String text, bool isDark) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 3, + ), + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.08), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Text( + text, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + ); + } + + Widget _buildCuisineExplore(bool isDark) { + final cuisineData = _getCuisineData(); + + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Text('🍜', style: TextStyle(fontSize: 18)), + const SizedBox(width: DesignTokens.space2), + Text( + '菜系探索度', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + const Spacer(), + Text( + '${cuisineData.length} 种菜系', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.dynamicPrimary, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + const SizedBox(height: DesignTokens.space3), + if (cuisineData.isEmpty) + Center( + child: Padding( + padding: const EdgeInsets.all(DesignTokens.space5), + child: Column( + children: [ + const Text('🗺️', style: TextStyle(fontSize: 32)), + const SizedBox(height: DesignTokens.space2), + Text( + '浏览更多菜谱,解锁你的菜系地图', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + ) + else ...[ + SizedBox( + height: 160, + child: PieChart( + PieChartData( + sections: _buildPieSections(cuisineData, isDark), + centerSpaceRadius: 36, + sectionsSpace: 2, + ), + ), + ), + const SizedBox(height: DesignTokens.space3), + Wrap( + spacing: DesignTokens.space2, + runSpacing: DesignTokens.space2, + children: cuisineData.entries.toList().asMap().entries.map((e) { + final color = + DesignTokens.categoryGradients[e.key % + DesignTokens.categoryGradients.length]; + final cuisineName = e.value.key; + final cuisineCount = e.value.value; + return Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 3, + ), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusSm, + border: Border.all(color: color.withValues(alpha: 0.2)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 6, + height: 6, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + const SizedBox(width: 4), + Text( + '$cuisineName $cuisineCount', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ], + ), + ); + } + + List _buildPieSections( + Map cuisineData, + bool isDark, + ) { + final total = cuisineData.values.fold(0, (a, b) => a + b); + if (total == 0) return []; + + final entries = cuisineData.entries.toList(); + return entries.asMap().entries.map((e) { + final index = e.key; + final entry = e.value; + final color = DesignTokens + .categoryGradients[index % DesignTokens.categoryGradients.length]; + final ratio = entry.value / total; + + return PieChartSectionData( + value: entry.value.toDouble(), + color: color, + radius: 22, + title: ratio >= 0.08 ? '${(ratio * 100).toStringAsFixed(0)}%' : '', + titleStyle: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w700, + color: isDark ? DarkDesignTokens.text1 : CupertinoColors.white, + ), + ); + }).toList(); + } + + Widget _buildSectionTitle(String title, bool isDark) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space1, + vertical: DesignTokens.space1, + ), + child: Text( + title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + ); + } + + void _triggerReactiveAccess() { + if (Get.isRegistered()) { + for (final _ in Get.find().history) { + break; + } + } + if (Get.isRegistered()) { + Get.find().favorites; + } + if (Get.isRegistered()) { + for (final _ in Get.find().notes) { + break; + } + } + if (Get.isRegistered()) { + Get.find().records; + } + } + + int _getBrowseCount() { + try { + if (!Get.isRegistered()) return 0; + return Get.find().history.length; + } catch (_) { + return 0; + } + } + + int _getFavoritesCount() { + try { + if (!Get.isRegistered()) return 0; + return Get.find().count; + } catch (_) { + return 0; + } + } + + int _getNotesCount() { + try { + if (!Get.isRegistered()) return 0; + return Get.find().notes.length; + } catch (_) { + return 0; + } + } + + int _getRatingCount() { + try { + if (!Get.isRegistered()) return 0; + return Get.find().count; + } catch (_) { + return 0; + } + } + + TastePreferenceService? _getTasteService() { + try { + if (!Get.isRegistered()) return null; + return Get.find(); + } catch (_) { + return null; + } + } + + int _getUsageDays() { + try { + if (!Get.isRegistered()) return 0; + final history = Get.find().history; + if (history.isEmpty) return 0; + final dates = {}; + for (final item in history) { + if (item.viewedAt.isNotEmpty) { + try { + dates.add(item.viewedAt.substring(0, 10)); + } catch (_) {} + } + } + if (dates.isEmpty) return 0; + final sorted = dates.toList()..sort(); + final first = DateTime.parse(sorted.first); + final now = DateTime.now(); + return now.difference(first).inDays + 1; + } catch (_) { + return 0; + } + } + + int _calculateRings(int browseCount) { + if (browseCount <= 0) return 0; + if (browseCount < 10) return 1; + if (browseCount < 30) return 2; + if (browseCount < 60) return 3; + if (browseCount < 100) return 4; + return 5; + } + + double _spiceToValue(SpiceLevel? level) { + return switch (level) { + SpiceLevel.none => 0.1, + SpiceLevel.mild => 0.3, + SpiceLevel.medium => 0.55, + SpiceLevel.hot => 0.8, + SpiceLevel.extreme => 1.0, + null => 0.3, + }; + } + + double _inferUmami() { + try { + if (!Get.isRegistered()) return 0.4; + final history = Get.find().history; + final umamiKeywords = ['炖', '煲', '煮', '蒸', '鲜', '汤', '熬']; + int matchCount = 0; + for (final item in history) { + final title = item.title.toLowerCase(); + if (umamiKeywords.any((k) => title.contains(k))) { + matchCount++; + } + } + if (history.isEmpty) return 0.4; + return (0.3 + (matchCount / history.length) * 0.7).clamp(0.1, 1.0); + } catch (_) { + return 0.4; + } + } + + double _inferAroma() { + try { + if (!Get.isRegistered()) return 0.4; + final history = Get.find().history; + final aromaKeywords = ['烤', '煎', '炒', '炸', '香', '焗', '烧']; + int matchCount = 0; + for (final item in history) { + final title = item.title.toLowerCase(); + if (aromaKeywords.any((k) => title.contains(k))) { + matchCount++; + } + } + if (history.isEmpty) return 0.4; + return (0.3 + (matchCount / history.length) * 0.7).clamp(0.1, 1.0); + } catch (_) { + return 0.4; + } + } + + String _getTastePersonality( + double spice, + double sweet, + double salty, + double sour, + ) { + final scores = { + '🌶 辣味爱好者': spice, + '🍬 甜食达人': sweet, + '🧂 咸鲜品味家': salty, + '🍋 酸爽探索者': sour, + }; + final max = scores.entries.reduce((a, b) => a.value > b.value ? a : b); + if (max.value < 0.4) return '🌈 均衡品味家'; + return max.key; + } + + List<_Milestone> _calculateMilestones() { + final browseCount = _getBrowseCount(); + final favCount = _getFavoritesCount(); + final noteCount = _getNotesCount(); + final ratingCount = _getRatingCount(); + + String? firstBrowseDate; + try { + if (Get.isRegistered()) { + final history = Get.find().history; + if (history.isNotEmpty) { + final sorted = history.toList() + ..sort((a, b) => a.viewedAt.compareTo(b.viewedAt)); + final raw = sorted.first.viewedAt; + if (raw.isNotEmpty) { + try { + final dt = DateTime.parse(raw); + firstBrowseDate = '${dt.year}.${dt.month}.${dt.day}'; + } catch (_) {} + } + } + } + } catch (_) {} + + return [ + _Milestone('📖', '初识美食', '首次浏览菜谱', browseCount >= 1, firstBrowseDate), + _Milestone('📚', '美食探索者', '浏览10道菜谱', browseCount >= 10, null), + _Milestone('🔍', '资深食客', '浏览50道菜谱', browseCount >= 50, null), + _Milestone('🏆', '美食百科', '浏览100道菜谱', browseCount >= 100, null), + _Milestone('❤️', '心动时刻', '首次收藏菜谱', favCount >= 1, null), + _Milestone('💎', '收藏家', '收藏10道菜谱', favCount >= 10, null), + _Milestone('✍️', '记录者', '写下第一条笔记', noteCount >= 1, null), + _Milestone('⭐', '品鉴师', '首次评分', ratingCount >= 1, null), + _Milestone( + '🎯', + '全能美食家', + '完成所有类别活动', + browseCount >= 1 && favCount >= 1 && noteCount >= 1 && ratingCount >= 1, + null, + ), + ]; + } + + List<_MonthData> _getMonthlyData() { + final result = {}; + + try { + if (Get.isRegistered()) { + final history = Get.find().history; + for (final item in history) { + if (item.viewedAt.isEmpty) continue; + try { + final dt = DateTime.parse(item.viewedAt); + final key = '${dt.year}-${dt.month.toString().padLeft(2, '0')}'; + result.putIfAbsent(key, () => _MonthData(key)); + result[key]!.browseCount++; + if (item.category != null && item.category!.isNotEmpty) { + result[key]!.categories[item.category!] = + (result[key]!.categories[item.category!] ?? 0) + 1; + } + } catch (_) {} + } + } + } catch (_) {} + + try { + if (Get.isRegistered()) { + final favs = Get.find().allFavorites; + for (final item in favs) { + final dateStr = item.createdAt ?? ''; + if (dateStr.isEmpty) continue; + try { + final dt = DateTime.parse(dateStr); + final key = '${dt.year}-${dt.month.toString().padLeft(2, '0')}'; + result.putIfAbsent(key, () => _MonthData(key)); + result[key]!.favCount++; + } catch (_) {} + } + } + } catch (_) {} + + try { + if (Get.isRegistered()) { + final notes = Get.find().notes; + for (final note in notes) { + if (note.createdAt.isEmpty) continue; + try { + final dt = DateTime.parse(note.createdAt); + final key = '${dt.year}-${dt.month.toString().padLeft(2, '0')}'; + result.putIfAbsent(key, () => _MonthData(key)); + result[key]!.noteCount++; + } catch (_) {} + } + } + } catch (_) {} + + try { + if (Get.isRegistered()) { + final records = Get.find().allRecords; + for (final record in records) { + if (record.ratedAt.isEmpty) continue; + try { + final dt = DateTime.parse(record.ratedAt); + final key = '${dt.year}-${dt.month.toString().padLeft(2, '0')}'; + result.putIfAbsent(key, () => _MonthData(key)); + result[key]!.ratingCount++; + } catch (_) {} + } + } + } catch (_) {} + + final sorted = result.values.toList() + ..sort((a, b) => b.key.compareTo(a.key)); + + return sorted.take(3).toList(); + } + + Map _getCuisineData() { + final cuisineCount = {}; + + try { + if (Get.isRegistered()) { + final history = Get.find().history; + for (final item in history) { + final cat = item.category; + if (cat != null && cat.isNotEmpty) { + cuisineCount[cat] = (cuisineCount[cat] ?? 0) + 1; + } + } + } + } catch (_) {} + + try { + if (Get.isRegistered()) { + final favs = Get.find().allFavorites; + for (final item in favs) { + final cat = item.categoryName; + if (cat != null && cat.isNotEmpty) { + cuisineCount[cat] = (cuisineCount[cat] ?? 0) + 1; + } + } + } + } catch (_) {} + + final sorted = cuisineCount.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); + + return Map.fromEntries(sorted.take(8)); + } +} + +class _RingPainter extends CustomPainter { + final int rings; + final Color primaryColor; + + _RingPainter({required this.rings, required this.primaryColor}); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final maxRadius = size.width / 2 - 4; + + for (int i = 0; i < 5; i++) { + final radius = maxRadius * (0.3 + i * 0.15); + final paint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = i < rings ? 3.0 : 1.0 + ..color = i < rings + ? primaryColor.withValues(alpha: 0.3 + i * 0.12) + : (primaryColor.withValues(alpha: 0.06)); + + canvas.drawCircle(center, radius, paint); + } + + if (rings > 0) { + final dotPaint = Paint()..color = primaryColor.withValues(alpha: 0.6); + final dotRadius = maxRadius * (0.3 + (rings - 1) * 0.15); + canvas.drawCircle( + Offset(center.dx + dotRadius * 0.707, center.dy + dotRadius * 0.707), + 4, + dotPaint, + ); + } + } + + @override + bool shouldRepaint(covariant _RingPainter oldDelegate) { + return oldDelegate.rings != rings || + oldDelegate.primaryColor != primaryColor; + } +} + +class _StatItem { + final String emoji; + final String label; + final int count; + final Color color; + + const _StatItem(this.emoji, this.label, this.count, this.color); +} + +class _Milestone { + final String emoji; + final String title; + final String description; + final bool unlocked; + final String? date; + + const _Milestone( + this.emoji, + this.title, + this.description, + this.unlocked, + this.date, + ); +} + +class _MonthData { + final String key; + int browseCount; + int favCount; + int noteCount; + int ratingCount; + final Map categories; + + _MonthData(this.key) + : browseCount = 0, + favCount = 0, + noteCount = 0, + ratingCount = 0, + categories = {}; + + String get label { + try { + final parts = key.split('-'); + return '${parts[0]}年${int.parse(parts[1])}月'; + } catch (_) { + return key; + } + } + + String? get topCategory { + if (categories.isEmpty) return null; + final sorted = categories.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); + return sorted.first.key; + } +} diff --git a/lib/src/pages/profile/info/about_page.dart b/lib/src/pages/profile/info/about_page.dart index 9149f74..d1f3026 100644 --- a/lib/src/pages/profile/info/about_page.dart +++ b/lib/src/pages/profile/info/about_page.dart @@ -203,7 +203,7 @@ class AboutPage extends StatelessWidget { Widget _buildDeveloperDocsSection(bool isDark) { return _buildSection( - title: '📚 开发者文档', + title: '📚 基础信息', isDark: isDark, children: [ _buildActionTile( @@ -223,28 +223,19 @@ class AboutPage extends StatelessWidget { ), _buildDivider(isDark), _buildActionTile( - icon: CupertinoIcons.globe, - title: 'API 基础地址', - subtitle: 'https://eat.wktyl.com/api/', + icon: CupertinoIcons.book, + title: '使用引导', + subtitle: '查看功能介绍与操作指引', isDark: isDark, - onTap: () => _openUrl('https://eat.wktyl.com/api/'), + onTap: () => Get.toNamed('/guide'), ), ], ); } - Future _openUrl(String url) async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri, mode: LaunchMode.externalApplication); - } else { - Get.snackbar('提示', '无法打开链接: $url', snackPosition: SnackPosition.BOTTOM); - } - } - Widget _buildContactSection(BuildContext context, bool isDark) { return _buildSection( - title: '📞 联系我们', + title: '📞 即时反馈', isDark: isDark, children: [ _buildActionTile( @@ -263,7 +254,7 @@ class AboutPage extends StatelessWidget { onTap: () { Get.snackbar( '提示', - '即将跳转到应用商店', + '未找到应用商店链接', snackPosition: SnackPosition.BOTTOM, ); }, @@ -459,11 +450,27 @@ class AboutPage extends StatelessWidget { void _showEmailSheet(BuildContext context, bool isDark) { final emails = [ - _ContactEmail(address: '2821981550@qq.com', label: 'QQ 邮箱', icon: '📬'), - _ContactEmail(address: '2572560133@qq.com', label: 'QQ 邮箱', icon: '📬'), - _ContactEmail(address: '5147662@qq.com', label: 'QQ 邮箱', icon: '📬'), - _ContactEmail(address: 'gg@0gg.cc', label: '自定义域名', icon: '🌐'), - _ContactEmail(address: 'ad@avefs.com', label: '商务合作', icon: '🤝'), + _ContactEmail( + address: 'gg@0gg.cc', + label: '若超24小时无回复可更换其他邮箱', + icon: '🌐', + ), + _ContactEmail( + address: 'ad@avefs.com', + label: '若超24小时无回复可更换其他邮箱', + icon: '🤝', + ), + _ContactEmail( + address: '2821981550@qq.com', + label: '任意邮箱均可联系', + icon: '📬', + ), + _ContactEmail( + address: '2572560133@qq.com', + label: '任意邮箱均可联系', + icon: '📬', + ), + _ContactEmail(address: '5147662@qq.com', label: '任意邮箱均可联系', icon: '📬'), ]; showCupertinoModalPopup( diff --git a/lib/src/pages/profile/info/app_info_page.dart b/lib/src/pages/profile/info/app_info_page.dart index 07a64f9..1391a47 100644 --- a/lib/src/pages/profile/info/app_info_page.dart +++ b/lib/src/pages/profile/info/app_info_page.dart @@ -219,7 +219,7 @@ class _AppInfoPageState extends State { ), const SizedBox(height: DesignTokens.space1), Text( - "Mom's Kitchen · 软件信息", + "Cute Kitchen · 软件信息", style: TextStyle( fontSize: DesignTokens.fontSm, color: CupertinoColors.white.withValues(alpha: 0.85), @@ -509,7 +509,12 @@ class _AppInfoPageState extends State { {'name': 'Dio', 'license': 'MIT'}, {'name': 'GetX', 'license': 'MIT'}, {'name': 'Hive CE', 'license': 'Apache 2.0'}, + {'name': 'fl_chart', 'license': 'BSD 3-Clause'}, {'name': 'url_launcher', 'license': 'BSD 3-Clause'}, + {'name': 'share_plus', 'license': 'BSD 3-Clause'}, + {'name': 'connectivity_plus', 'license': 'Apache 2.0'}, + {'name': 'permission_handler', 'license': 'MIT'}, + {'name': 'cached_network_image', 'license': 'MIT'}, {'name': 'package_info_plus', 'license': 'BSD 3-Clause'}, {'name': 'device_info_plus', 'license': 'BSD 3-Clause'}, ]; @@ -619,8 +624,8 @@ class _AppInfoPageState extends State { _buildInfoItem('Web 服务器', 'Nginx', CupertinoIcons.globe, isDark), _buildDivider(isDark), _buildCopyableItem( - 'API 地址', - AppConfig.baseUrl, + 'App 在线版(Beta测试)', + 'https://eat.wktyl.com/app', CupertinoIcons.link, isDark, primaryColor, @@ -784,16 +789,16 @@ class _AppInfoPageState extends State { children: [ _buildUpdateItem( '版本 ${AppConfig.appVersion}', - '2026-04', - ['新增软件信息页面、技术栈展示', '优化关于页面布局与交互体验'], + '2026-04-21', + ['升级鸿蒙api 23', '优化关于页面布局与交互体验'], isDark, primaryColor, ), const SizedBox(height: DesignTokens.space3), _buildUpdateItem( '版本 0.92.4', - '2026-04', - ['新增关于页面、权限页面、参考文献页面', '优化主题系统与毛玻璃效果'], + '2026-04-10', + ['Release细节完善', '优化主题系统与毛玻璃效果'], isDark, primaryColor, ), diff --git a/lib/src/pages/profile/info/learn_us_page.dart b/lib/src/pages/profile/info/learn_us_page.dart index 0db5f1d..42c2f1e 100644 --- a/lib/src/pages/profile/info/learn_us_page.dart +++ b/lib/src/pages/profile/info/learn_us_page.dart @@ -5,6 +5,7 @@ * 创建: 2026-04-18 * 更新: 2026-04-18 新增了解我们页面 * 更新: 2026-04-18 头部emoji改用图片,版本号同步更新 + * 更新: 2026-04-21 邮箱入口改为多邮箱弹窗选择样式 */ import 'package:flutter/cupertino.dart'; @@ -49,7 +50,7 @@ class LearnUsPage extends StatelessWidget { const SizedBox(height: DesignTokens.space4), _buildOfficialSiteSection(isDark), const SizedBox(height: DesignTokens.space4), - _buildDeveloperSection(isDark), + _buildDeveloperSection(context, isDark), const SizedBox(height: DesignTokens.space4), _buildTeamSection(isDark), const SizedBox(height: DesignTokens.space4), @@ -137,7 +138,7 @@ class LearnUsPage extends StatelessWidget { ), const SizedBox(height: DesignTokens.space1), Text( - '用心烹饪,用爱生活', + '用心开发,只为更好的体验', style: TextStyle( fontSize: DesignTokens.fontSm, color: CupertinoColors.white.withValues(alpha: 0.85), @@ -203,22 +204,22 @@ class LearnUsPage extends StatelessWidget { ), _buildLinkTile( icon: CupertinoIcons.globe, - label: 'API 服务', - url: 'https://eat.wktyl.com/api/', + label: '工作室官网', + url: 'https://wktyl.com/', isDark: isDark, ), _buildDivider(isDark), _buildLinkTile( icon: CupertinoIcons.doc_text, - label: 'App 接入指南', - url: 'https://eat.wktyl.com/api/doc/APP_GUIDE.md', + label: '小妈厨房下载页面', + url: 'https://eat.wktyl.com/', isDark: isDark, ), _buildDivider(isDark), _buildLinkTile( icon: CupertinoIcons.book, - label: 'API 文档', - url: 'https://eat.wktyl.com/api/doc/API_DOC.md', + label: '其他产品', + url: 'https://poe.vogov.cn/app.html', isDark: isDark, ), ], @@ -226,7 +227,7 @@ class LearnUsPage extends StatelessWidget { } /// 开发者区域 - Widget _buildDeveloperSection(bool isDark) { + Widget _buildDeveloperSection(BuildContext context, bool isDark) { return _buildSection( title: '🏢 开发者', isDark: isDark, @@ -288,12 +289,7 @@ class LearnUsPage extends StatelessWidget { ), ), _buildDivider(isDark), - _buildCopyTile( - icon: CupertinoIcons.mail, - title: '商务合作 & 联系我们', - value: 'support@momkitchen.app', - isDark: isDark, - ), + _buildEmailContactTile(context, isDark), _buildDivider(isDark), Padding( padding: const EdgeInsets.symmetric( @@ -437,7 +433,7 @@ class LearnUsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '滇ICP备2022000863号-15A', + '滇ICP备2022000863号-16A', style: TextStyle( fontSize: DesignTokens.fontMd, color: isDark @@ -606,77 +602,6 @@ class LearnUsPage extends StatelessWidget { ); } - /// 可复制的文本项 - Widget _buildCopyTile({ - required IconData icon, - required String title, - required String value, - required bool isDark, - }) { - return GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: value)); - Get.snackbar( - '复制成功', - '已复制到剪贴板', - snackPosition: SnackPosition.BOTTOM, - duration: const Duration(seconds: 2), - ); - }, - behavior: HitTestBehavior.opaque, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space4, - vertical: DesignTokens.space3, - ), - child: Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: DesignTokens.blue.withValues(alpha: 0.12), - borderRadius: DesignTokens.borderRadiusSm, - ), - child: Icon(icon, size: 20, color: DesignTokens.blue), - ), - const SizedBox(width: DesignTokens.space3), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w500, - color: isDark - ? DarkDesignTokens.text1 - : DesignTokens.text1, - ), - ), - const SizedBox(height: 2), - Text( - value, - style: TextStyle( - fontSize: DesignTokens.fontSm, - color: DesignTokens.blue, - ), - ), - ], - ), - ), - Icon( - CupertinoIcons.doc_on_clipboard, - size: 18, - color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, - ), - ], - ), - ), - ); - } - /// 团队成员项 Widget _buildTeamMember( String emoji, @@ -760,6 +685,336 @@ class LearnUsPage extends StatelessWidget { ); } + /// 邮箱联系项 + Widget _buildEmailContactTile(BuildContext context, bool isDark) { + return GestureDetector( + onTap: () => _showEmailSheet(context, isDark), + behavior: HitTestBehavior.opaque, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space3, + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: DesignTokens.blue.withValues(alpha: 0.12), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Icon( + CupertinoIcons.mail, + size: 20, + color: DesignTokens.blue, + ), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '商务合作 & 联系我们 & 侵权申诉', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const SizedBox(height: 2), + Text( + '点击查看多个联系邮箱', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.blue, + ), + ), + ], + ), + ), + Icon( + CupertinoIcons.arrow_up_right_square, + size: 18, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + ), + ); + } + + void _showEmailSheet(BuildContext context, bool isDark) { + final emails = [ + _ContactEmail( + address: 'gg@0gg.cc', + label: '若超24小时无回复可更换其他邮箱', + icon: '🌐', + ), + _ContactEmail( + address: 'ad@avefs.com', + label: '若超24小时无回复可更换其他邮箱', + icon: '🤝', + ), + _ContactEmail( + address: '2821981550@qq.com', + label: '任意邮箱均可联系', + icon: '📬', + ), + _ContactEmail( + address: '2572560133@qq.com', + label: '任意邮箱均可联系', + icon: '📬', + ), + _ContactEmail(address: '5147662@qq.com', label: '任意邮箱均可联系', icon: '📬'), + ]; + + showCupertinoModalPopup( + context: context, + builder: (ctx) => Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(ctx).size.height * 0.7, + ), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(DesignTokens.radiusXl), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: DesignTokens.space2), + Container( + width: 36, + height: 4, + decoration: BoxDecoration( + color: (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.3), + borderRadius: DesignTokens.borderRadiusSm, + ), + ), + const SizedBox(height: DesignTokens.space3), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + ), + child: Row( + children: [ + const Text('📧', style: TextStyle(fontSize: 20)), + const SizedBox(width: DesignTokens.space2), + Text( + '联系邮箱', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const Spacer(), + GestureDetector( + onTap: () => Navigator.pop(ctx), + child: Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: + (isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3) + .withValues(alpha: 0.12), + shape: BoxShape.circle, + ), + child: Icon( + CupertinoIcons.xmark, + size: 14, + color: isDark + ? DarkDesignTokens.text2 + : DesignTokens.text2, + ), + ), + ), + ], + ), + ), + const SizedBox(height: DesignTokens.space2), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.06), + borderRadius: DesignTokens.borderRadiusLg, + border: Border.all( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.15), + ), + ), + child: Row( + children: [ + Icon( + CupertinoIcons.info_circle, + size: 16, + color: DesignTokens.dynamicPrimary, + ), + const SizedBox(width: DesignTokens.space2), + Expanded( + child: Text( + '发送邮件时请在主题中备注「小妈厨房」,以便我们快速识别处理', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: DesignTokens.dynamicPrimary, + height: 1.5, + ), + ), + ), + ], + ), + ), + ), + const SizedBox(height: DesignTokens.space3), + Flexible( + child: ListView.separated( + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + ), + itemCount: emails.length, + separatorBuilder: (_, __) => + const SizedBox(height: DesignTokens.space2), + itemBuilder: (_, index) => + _buildEmailCard(emails[index], isDark, ctx), + ), + ), + const SizedBox(height: DesignTokens.space4), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric( + vertical: DesignTokens.space3, + ), + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.08), + borderRadius: DesignTokens.borderRadiusLg, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + CupertinoIcons.reply, + size: 16, + color: DesignTokens.dynamicPrimary, + ), + const SizedBox(width: DesignTokens.space2), + Text( + '任意邮箱均可联系我们', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: DesignTokens.dynamicPrimary, + ), + ), + ], + ), + ), + ), + SizedBox( + height: MediaQuery.of(ctx).padding.bottom + DesignTokens.space3, + ), + ], + ), + ), + ); + } + + Widget _buildEmailCard( + _ContactEmail email, + bool isDark, + BuildContext sheetContext, + ) { + return GestureDetector( + onTap: () { + final uri = Uri( + scheme: 'mailto', + path: email.address, + queryParameters: {'subject': '小妈厨房 - 用户反馈'}, + ); + launchUrl(uri, mode: LaunchMode.externalApplication); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.background : DesignTokens.background, + borderRadius: DesignTokens.borderRadiusLg, + border: Border.all( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.12) + : DesignTokens.text3.withValues(alpha: 0.12), + ), + ), + child: Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: DesignTokens.dynamicPrimary.withValues(alpha: 0.08), + borderRadius: DesignTokens.borderRadiusMd, + ), + child: Center( + child: Text(email.icon, style: const TextStyle(fontSize: 16)), + ), + ), + const SizedBox(width: DesignTokens.space3), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + email.address, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const SizedBox(height: 2), + Text( + email.label, + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + Icon( + CupertinoIcons.arrow_up_right_square, + size: 16, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + ), + ); + } + /// 分割线 Widget _buildDivider(bool isDark) { return Padding( @@ -902,3 +1157,15 @@ class LearnUsPage extends StatelessWidget { } } } + +class _ContactEmail { + final String address; + final String label; + final String icon; + + const _ContactEmail({ + required this.address, + required this.label, + required this.icon, + }); +} diff --git a/lib/src/pages/profile/info/privacy_policy_page.dart b/lib/src/pages/profile/info/privacy_policy_page.dart index 8dab79b..d7e86c8 100644 --- a/lib/src/pages/profile/info/privacy_policy_page.dart +++ b/lib/src/pages/profile/info/privacy_policy_page.dart @@ -26,7 +26,7 @@ class PrivacyPolicyContent extends StatelessWidget { _buildUpdateDate('2026.4.17'), const SizedBox(height: DesignTokens.space5), _buildParagraph( - '小妈厨房 是由 弥勒市朋普镇微风暴网络科技工作室 (以下简称"我们")为您提供的,用于健康饮食管理、菜谱浏览与烹饪辅助的应用。本隐私声明由我们为处理您的个人信息而制定。', + '小妈厨房 是由 弥勒市朋普镇微风暴网络科技工作室 (以下简称"我们")为您提供的,用于食谱大全管理、菜谱浏览与烹饪辅助的应用。本隐私声明由我们为处理您的个人信息而制定。', ), const SizedBox(height: DesignTokens.space4), _buildParagraph( @@ -56,6 +56,8 @@ class PrivacyPolicyContent extends StatelessWidget { _buildPermissionItem('🔔 通知权限', '用于在用餐提醒、烹饪计时等场景发送通知'), _buildPermissionItem('📷 相机权限', '用于扫描二维码、拍摄菜品照片等功能'), _buildPermissionItem('🔗 分享能力', '调用系统分享功能,分享菜谱、购物清单等数据'), + _buildPermissionItem('🔗 文件读写', '调用系统安全文件接口,管理导出导入等文件'), + const SizedBox(height: DesignTokens.space5), _buildSectionTitle('3. 管理您的个人信息'), const SizedBox(height: DesignTokens.space4), @@ -75,7 +77,7 @@ class PrivacyPolicyContent extends StatelessWidget { const SizedBox(height: DesignTokens.space3), _buildContactInfo('👤 开发者', '弥勒市朋普镇微风暴网络科技工作室'), _buildContactInfo('📍 地址', '云南 昆明 西山区'), - _buildContactInfo('📧 邮箱', 'support@momkitchen.app'), + _buildContactInfo('📧 邮箱', '2821981550@qq.com'), const SizedBox(height: DesignTokens.space4), _buildParagraph( '如果您对我们的回复不满意,特别是当个人信息处理行为损害了您的合法权益时,您还可以通过向有管辖权的人民法院提起诉讼、向行业自律协会或政府相关管理机构投诉等外部途径进行解决。', diff --git a/lib/src/pages/profile/profile_home.dart b/lib/src/pages/profile/profile_home.dart index 8b61d86..c3b2d06 100644 --- a/lib/src/pages/profile/profile_home.dart +++ b/lib/src/pages/profile/profile_home.dart @@ -4,14 +4,13 @@ * 作用: iOS 26 风格的用户信息展示、功能入口和消息预览 * 更新: 2026-04-09 重构为 Liquid Glass 风格,统一使用 DesignTokens * 更新: 2026-04-12 添加滚动物理特性,支持上下滑动 + * 更新: 2026-04-21 用户卡片改为开发计划卡片,点击弹出路线图 Sheet */ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/config/app_config.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; -import 'package:mom_kitchen/src/controllers/user/profile_controller.dart'; -import 'package:mom_kitchen/src/pages/profile/settings/personalization_page.dart'; import 'package:mom_kitchen/src/config/app_routes.dart'; class ProfileHomeTab extends StatelessWidget { @@ -19,32 +18,29 @@ class ProfileHomeTab extends StatelessWidget { @override Widget build(BuildContext context) { - final profileController = Get.find(); final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; - return Obx( - () => SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics( - parent: BouncingScrollPhysics(), - ), - padding: const EdgeInsets.symmetric( - horizontal: DesignTokens.space4, - vertical: DesignTokens.space2, - ), - child: Column( - children: [ - _buildUserCard(profileController, isDark), - const SizedBox(height: DesignTokens.space4), - _buildFeatureGrid(isDark), - const SizedBox(height: DesignTokens.space4), - _buildToolsGrid(isDark), - const SizedBox(height: DesignTokens.space4), - _buildMessagePreview(isDark), - const SizedBox(height: DesignTokens.space5), - _buildAppFooter(isDark), - const SizedBox(height: DesignTokens.space6), - ], - ), + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics(), + ), + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space2, + ), + child: Column( + children: [ + _buildDevPlanCard(isDark), + const SizedBox(height: DesignTokens.space4), + _buildFeatureGrid(isDark), + const SizedBox(height: DesignTokens.space4), + _buildToolsGrid(isDark), + const SizedBox(height: DesignTokens.space4), + _buildMessagePreview(isDark), + const SizedBox(height: DesignTokens.space5), + _buildAppFooter(isDark), + const SizedBox(height: DesignTokens.space6), + ], ), ); } @@ -155,116 +151,258 @@ class ProfileHomeTab extends StatelessWidget { ); } - Widget _buildUserCard(ProfileController controller, bool isDark) { - final user = controller.user.value; - final isLoggedIn = controller.isLoggedIn.value; + Widget _buildDevPlanCard(bool isDark) { + return GestureDetector( + onTap: () => _showDevPlanSheet(isDark), + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + borderRadius: DesignTokens.borderRadiusLg, + boxShadow: DesignTokens.shadowsSm, + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '开发计划', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const SizedBox(height: DesignTokens.space1), + Text( + '查看即将推出的功能与路线图', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], + ), + ), + Icon( + CupertinoIcons.chevron_right, + size: 18, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ], + ), + ), + ); + } + + void _showDevPlanSheet(bool isDark) { + final plans = <_DevPlanItem>[ + _DevPlanItem('📱', '多端适配', '即将支持平板端与 Web 端,一套代码多端运行', _PlanStatus.coming), + _DevPlanItem( + '👤', + '用户中心', + '即将支持登录功能,后续需付费激活注册,大部分功能无需登录即可正常使用', + _PlanStatus.coming, + ), + _DevPlanItem('🛠️', '工具中心', '支持导入自定义工具,打造专属厨房工具箱', _PlanStatus.dev), + _DevPlanItem( + '📄', + '导出功能', + '菜品详情信息支持导出 PDF / Docs 文档,方便分享与打印', + _PlanStatus.dev, + ), + _DevPlanItem('✍️', '菜品投稿', '开发菜品投稿功能,分享你的独家食谱', _PlanStatus.dev), + _DevPlanItem('💬', '交流反馈', '点赞、关注,讨论,与美食爱好者交流互动', _PlanStatus.plan), + _DevPlanItem('🧠', '智能推荐', '基于口味偏好与历史记录,智能推荐你可能喜欢的菜品', _PlanStatus.plan), + _DevPlanItem('🥬', '食材管理', '食材库存追踪与保质期提醒,减少浪费合理规划', _PlanStatus.plan), + _DevPlanItem('📊', '营养分析', '菜品营养成分详细分析,热量/蛋白质/碳水一目了然', _PlanStatus.plan), + _DevPlanItem('🌍', '多语言支持', '支持英文等多语言界面,让美食无国界', _PlanStatus.plan), + _DevPlanItem('☁️', '云同步', '跨设备数据同步,收藏与偏好随身携带', _PlanStatus.plan), + _DevPlanItem('🔔', '消息通知', '新菜谱推送、互动提醒,不错过任何精彩内容', _PlanStatus.plan), + ]; + + showCupertinoModalPopup( + context: Get.context!, + builder: (context) { + final sheetDark = + CupertinoTheme.brightnessOf(context) == Brightness.dark; + return Container( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.75, + ), + decoration: BoxDecoration( + color: sheetDark + ? DarkDesignTokens.background + : DesignTokens.background, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(DesignTokens.radiusLg), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + vertical: DesignTokens.space3, + ), + child: Container( + width: 36, + height: 5, + decoration: BoxDecoration( + color: sheetDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + borderRadius: DesignTokens.borderRadiusFull, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + ), + child: Row( + children: [ + const Text('🚀', style: TextStyle(fontSize: 22)), + const SizedBox(width: DesignTokens.space2), + Text( + '开发计划', + style: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w700, + color: sheetDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const Spacer(), + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + onPressed: () => Navigator.of(context).pop(), + child: Text( + '关闭', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.dynamicPrimary, + ), + ), + ), + ], + ), + ), + const SizedBox(height: DesignTokens.space2), + Flexible( + child: ListView.separated( + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space4, + vertical: DesignTokens.space2, + ), + itemCount: plans.length, + separatorBuilder: (_, _) => + const SizedBox(height: DesignTokens.space2), + itemBuilder: (context, index) { + final plan = plans[index]; + return _buildDevPlanItem(plan, sheetDark); + }, + ), + ), + SizedBox( + height: + MediaQuery.of(context).padding.bottom + DesignTokens.space3, + ), + ], + ), + ); + }, + ); + } + + Widget _buildDevPlanItem(_DevPlanItem plan, bool isDark) { + final statusColor = switch (plan.status) { + _PlanStatus.coming => DesignTokens.green, + _PlanStatus.dev => DesignTokens.orange, + _PlanStatus.plan => DesignTokens.purple, + }; + final statusLabel = switch (plan.status) { + _PlanStatus.coming => '即将上线', + _PlanStatus.dev => '开发中', + _PlanStatus.plan => '规划中', + }; return Container( - padding: const EdgeInsets.all(DesignTokens.space4), + padding: const EdgeInsets.all(DesignTokens.space3), decoration: BoxDecoration( color: isDark ? DarkDesignTokens.card : DesignTokens.card, - borderRadius: DesignTokens.borderRadiusLg, + borderRadius: DesignTokens.borderRadiusMd, boxShadow: DesignTokens.shadowsSm, ), child: Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - width: 56, - height: 56, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: DesignTokens.primaryLight, - ), - child: isLoggedIn && user != null && user.avatar != null - ? ClipOval( - child: Image.network( - user.avatar!, - width: 56, - height: 56, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) { - return Icon( - CupertinoIcons.person_fill, - size: 28, - color: DesignTokens.dynamicPrimary, - ); - }, - ), - ) - : Icon( - CupertinoIcons.person_fill, - size: 28, - color: DesignTokens.dynamicPrimary, - ), - ), + Text(plan.emoji, style: const TextStyle(fontSize: 24)), const SizedBox(width: DesignTokens.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - isLoggedIn && user != null && user.isNotEmpty - ? user.name - : '未登录', - style: TextStyle( - fontSize: DesignTokens.fontLg, - fontWeight: FontWeight.w700, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), + Row( + children: [ + Text( + plan.title, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + const SizedBox(width: DesignTokens.space2), + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 2, + ), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.12), + borderRadius: DesignTokens.borderRadiusSm, + ), + child: Text( + statusLabel, + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w500, + color: statusColor, + ), + ), + ), + ], ), const SizedBox(height: DesignTokens.space1), Text( - isLoggedIn && user != null && user.isNotEmpty - ? user.email - : '点击登录', + plan.description, style: TextStyle( fontSize: DesignTokens.fontSm, color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + height: 1.4, ), ), ], ), ), - const SizedBox(width: DesignTokens.space2), - _buildMiniButton( - isLoggedIn ? '编辑' : '登录', - isDark, - isLoggedIn - ? null - : () => controller.login('test@example.com', '123456'), - ), - const SizedBox(width: DesignTokens.space2), - _buildMiniButton( - '个性化', - isDark, - () => Get.to(() => const PersonalizationPage()), - ), ], ), ); } - Widget _buildMiniButton(String text, bool isDark, VoidCallback? onPressed) { - return CupertinoButton( - padding: EdgeInsets.symmetric( - horizontal: DesignTokens.space3, - vertical: DesignTokens.space1, - ), - minimumSize: Size.zero, - borderRadius: DesignTokens.borderRadiusSm, - color: DesignTokens.dynamicPrimary, - onPressed: onPressed, - child: Text( - text, - style: const TextStyle( - fontSize: DesignTokens.fontXs, - fontWeight: FontWeight.w500, - color: CupertinoColors.white, - ), - ), - ); - } - Widget _buildFeatureGrid(bool isDark) { final items = [ _FeatureItem( @@ -463,7 +601,7 @@ class ProfileHomeTab extends StatelessWidget { ), SizedBox(height: DesignTokens.space2), Text( - 'v${AppConfig.appVersion} · 2026 Liquid Glass', + 'v${AppConfig.appVersion} · 2026.4 Cute kitchen', style: TextStyle( fontSize: DesignTokens.fontXs, color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, @@ -490,3 +628,14 @@ class _FeatureItem { const _FeatureItem(this.icon, this.label, this.color, this.route); } + +enum _PlanStatus { coming, dev, plan } + +class _DevPlanItem { + final String emoji; + final String title; + final String description; + final _PlanStatus status; + + const _DevPlanItem(this.emoji, this.title, this.description, this.status); +} diff --git a/lib/src/pages/profile/profile_settings.dart b/lib/src/pages/profile/profile_settings.dart index d9006ee..cc08a49 100644 --- a/lib/src/pages/profile/profile_settings.dart +++ b/lib/src/pages/profile/profile_settings.dart @@ -12,20 +12,19 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/config/app_config.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; -import 'package:mom_kitchen/src/controllers/user/profile_controller.dart'; import 'package:mom_kitchen/src/pages/profile/settings/personalization_page.dart'; import 'package:mom_kitchen/src/pages/profile/settings/preference_page.dart'; import 'package:mom_kitchen/src/pages/tools/cooking/cooking_note_page.dart'; import 'package:mom_kitchen/src/pages/profile/social/footprints_page.dart'; import 'package:mom_kitchen/src/pages/profile/data/cache_manage_page.dart'; import 'package:mom_kitchen/src/pages/profile/tools/data_export_page.dart'; +import 'package:mom_kitchen/src/config/app_routes.dart'; class ProfileSettingsTab extends StatelessWidget { const ProfileSettingsTab({super.key}); @override Widget build(BuildContext context) { - final profileController = Get.find(); final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; return Obx( @@ -118,20 +117,18 @@ class ProfileSettingsTab extends StatelessWidget { ], ), const SizedBox(height: DesignTokens.space3), - if (profileController.isLoggedIn.value) - _buildSection( - title: '👤 账户', - isDark: isDark, - children: [ - _buildTile( - icon: CupertinoIcons.power, - title: '退出登录', - isDark: isDark, - titleColor: DesignTokens.red, - onTap: profileController.logout, - ), - ], - ), + _buildSection( + title: '🌟 美食年轮', + isDark: isDark, + children: [ + _buildTile( + icon: CupertinoIcons.circle_grid_3x3, + title: '查看你的美食旅程', + isDark: isDark, + onTap: () => Get.toNamed(AppRoutes.foodTimeline), + ), + ], + ), const SizedBox(height: DesignTokens.space6), _buildAppFooter(isDark), ], @@ -276,7 +273,7 @@ class ProfileSettingsTab extends StatelessWidget { ), SizedBox(height: DesignTokens.space3), Text( - '让每一餐都充满爱与温度 ❤️', + '再忙也要记得吃饭,保重身体喔 ❤️', style: TextStyle( fontSize: DesignTokens.fontXs, color: DesignTokens.dynamicPrimary.withValues(alpha: 0.6), @@ -292,7 +289,13 @@ class ProfileSettingsTab extends StatelessWidget { icon: '🌐', title: 'VPN/代理环境部分功能异常', level: _IssueLevel.warning, - description: '使用代理或 VPN 时,部分接口请求可能超时或失败,建议在直连环境下使用以获得最佳体验。', + description: '使用代理或 VPN 时,部分接口请求可能超时或失败,建议在直连环境或者国内网络环境下使用以获得最佳体验。', + ), + _KnownIssue( + icon: '🧊', + title: 'Win10/11端 页面返回问题', + level: _IssueLevel.warning, + description: '部分页面未设置返回逻辑,需使用快捷键 同时按下 ALT+方向左键/Backspace(删除键) 返回上一页。', ), _KnownIssue( icon: '📂', diff --git a/lib/src/pages/profile/settings/personalization_page.dart b/lib/src/pages/profile/settings/personalization_page.dart index 5ff47ad..df987fa 100644 --- a/lib/src/pages/profile/settings/personalization_page.dart +++ b/lib/src/pages/profile/settings/personalization_page.dart @@ -88,8 +88,9 @@ class PersonalizationPage extends StatelessWidget { children: [_buildAnimationItem(controller, themeService)], ), CupertinoListSection.insetGrouped( - header: const Text('🌐 语言'), - children: _buildLanguageItems(controller, themeService), + header: const Text('🌐 语言 (即将支持多语言设置) '), + // children: _buildLanguageItems(controller, themeService), + // TODO: 实现多语言设置 ), CupertinoListSection.insetGrouped( header: const Text('💬 对话框样式'), diff --git a/lib/src/pages/profile/social/chat_page.dart b/lib/src/pages/profile/social/chat_page.dart index dcee9f3..1eb819c 100644 --- a/lib/src/pages/profile/social/chat_page.dart +++ b/lib/src/pages/profile/social/chat_page.dart @@ -1,35 +1,15 @@ -/* - * 文件: feedback_page.dart - * 名称: 意见反馈页面 - * 作用: iOS 26 风格的聊天式意见反馈页面,客服在左用户在右 - * 更新: 2026-04-10 从聊天示例页改造为聊天式意见反馈页 - */ +// 2026-04-21 | feedback_page.dart | 聊天式意见反馈页面 | iOS 26风格,支持5轮对话、邮件发送、自动回复 +// 2026-04-10 | 从聊天示例页改造为聊天式意见反馈页 +// 2026-04-21 | 重构为完整反馈系统:状态机驱动、5轮限制、双发邮件、本地存储 +import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:mom_kitchen/src/config/design_tokens.dart'; - -enum FeedbackType { - bug('\u{1F41B}', 'Bug 反馈'), - feature('\u{2728}', '功能建议'), - experience('\u{1F44D}', '体验优化'), - other('\u{1F4AC}', '其他'); - - final String emoji; - final String label; - const FeedbackType(this.emoji, this.label); -} - -class _ChatMessage { - final String text; - final bool isUser; - final FeedbackType? feedbackType; - - const _ChatMessage({ - required this.text, - required this.isUser, - this.feedbackType, - }); -} +import 'package:mom_kitchen/src/models/feedback_model.dart'; +import 'package:mom_kitchen/src/services/data/business/email_service.dart'; +import 'package:mom_kitchen/src/services/local/feedback_storage_service.dart'; +import 'package:mom_kitchen/src/services/core/user_service.dart'; +import 'package:mom_kitchen/src/repositories/action_repository.dart'; class FeedbackPage extends StatefulWidget { const FeedbackPage({super.key}); @@ -39,19 +19,35 @@ class FeedbackPage extends StatefulWidget { } class _FeedbackPageState extends State { + // ═══════════════════════════════════════════════════════════════ + // 状态管理(状态机模式) + // ═══════════════════════════════════════════════════════════════ + + FeedbackState _currentState = FeedbackState.idle; + FeedbackSession? _session; final TextEditingController _ctrl = TextEditingController(); + final TextEditingController _emailCtrl = TextEditingController(); final ScrollController _scrollCtrl = ScrollController(); final List<_ChatMessage> _messages = []; - FeedbackType? _selectedType; - bool _typeSelected = false; + bool _isSending = false; + String? _errorMessage; @override void initState() { super.initState(); - _addBotMessage('你好!\u{1F44B} 欢迎使用意见反馈'); + _initConversation(); + } + + /// 初始化对话流程 + void _initConversation() { + setState(() => _currentState = FeedbackState.idle); + + _addBotMessage('你好!👋 欢迎使用意见反馈'); + Future.delayed(const Duration(milliseconds: 600), () { if (mounted) { _addBotMessage('请选择您要反馈的类型:'); + setState(() => _currentState = FeedbackState.selecting); } }); } @@ -59,10 +55,15 @@ class _FeedbackPageState extends State { @override void dispose() { _ctrl.dispose(); + _emailCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } + // ═══════════════════════════════════════════════════════════════ + // 消息处理 + // ═══════════════════════════════════════════════════════════════ + void _addBotMessage(String text) { setState(() { _messages.add(_ChatMessage(text: text, isUser: false)); @@ -89,39 +90,481 @@ class _FeedbackPageState extends State { }); } + // ═══════════════════════════════════════════════════════════════ + // 状态转换逻辑 + // ═══════════════════════════════════════════════════════════════ + + /// 处理类型选择 void _onTypeSelected(FeedbackType type) { - if (_typeSelected) return; - setState(() => _typeSelected = true); - _selectedType = type; + if (_currentState != FeedbackState.selecting) return; + + // 创建新的反馈会话 + _createSession(type); + + setState(() => _currentState = FeedbackState.chatting_1); _addUserMessage('${type.emoji} ${type.label}', type: type); Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { - _addBotMessage('好的,您选择了「${type.label}」,请详细描述您的问题或建议:'); + _addBotMessage( + '好的,您选择了「${type.label}」,请详细描述您的问题或建议:\n\n💡 提示:您可以分多次描述,我们共可记录4条消息', + ); } }); } + /// 创建反馈会话(收集设备信息) + Future _createSession(FeedbackType type) async { + String uuid = ''; + String ip = ''; + String appVersion = ''; + String platform = Platform.operatingSystem; + String osVersion = ''; + String logs = ''; + + try { + uuid = await UserService().getUserId(); + } catch (e) { + debugPrint('获取设备UUID失败: $e'); + } + + try { + final ipStatus = await ActionRepository().fetchIpStatus(); + ip = ipStatus.ip.toString(); + } catch (e) { + debugPrint('获取IP地址失败: $e'); + } + + try { + // 尝试获取应用版本信息 + appVersion = '1.3.0'; // 从pubspec.yaml读取或使用PackageInfo + } catch (e) { + debugPrint('获取版本号失败: $e'); + } + + osVersion = Platform.operatingSystemVersion; + logs = + '[$DateTime.now()] 反馈系统初始化完成\n' + '平台: $platform $osVersion\n' + '版本: v$appVersion'; + + setState(() { + _session = FeedbackSession.create( + type: type, + deviceInfo: DeviceInfo( + uuid: uuid, + ip: ip, + appVersion: appVersion, + platform: platform, + osVersion: osVersion, + logs: logs, + ), + ); + }); + } + + /// 处理用户发送消息 void _send() { final text = _ctrl.text.trim(); if (text.isEmpty) return; + // 记录到会话中 + if (_session != null) { + final userMsg = FeedbackMessage( + text: text, + isUser: true, + timestamp: DateTime.now(), + ); + _session = _session!.addMessage(userMsg); + } + _addUserMessage(text); _ctrl.clear(); + // 根据当前状态推进到下一轮 + _advanceToNextRound(); + } + + /// 推进到下一轮对话 + void _advanceToNextRound() { Future.delayed(const Duration(milliseconds: 800), () { if (!mounted) return; - _addBotMessage( - '感谢您的反馈!\u{2705} 我们会尽快处理您的${_selectedType?.label ?? '意见'}。', - ); - Future.delayed(const Duration(milliseconds: 600), () { - if (mounted) { - _addBotMessage('还有其他问题吗?可以继续输入,或直接返回 \u{1F44B}'); - } - }); + + final currentRound = _session?.currentRound ?? 0; + + switch (_currentState) { + case FeedbackState.chatting_1: + if (currentRound >= 1) { + setState(() => _currentState = FeedbackState.chatting_2); + _addBotMessage('收到!还有其他需要补充的吗?🤔 (第2轮,还可继续2轮)'); + + // 添加Bot回复到会话 + _session = _session?.addMessage( + FeedbackMessage( + text: '收到!还有其他需要补充的吗?', + isUser: false, + timestamp: DateTime.now(), + ), + ); + } + break; + + case FeedbackState.chatting_2: + if (currentRound >= 2) { + setState(() => _currentState = FeedbackState.chatting_3); + _addBotMessage('好的,已记录!如果还有细节可以继续说明 📝 (第3轮,还可继续1轮)'); + + _session = _session?.addMessage( + FeedbackMessage( + text: '好的,已记录!如果还有细节可以继续说明', + isUser: false, + timestamp: DateTime.now(), + ), + ); + } + break; + + case FeedbackState.chatting_3: + if (currentRound >= 3) { + setState(() => _currentState = FeedbackState.chatting_4); + _addBotMessage('这是最后一轮了 ✨ 请补充最后的信息,或者直接点击下方「提交反馈」按钮'); + + _session = _session?.addMessage( + FeedbackMessage( + text: '这是最后一轮了,请补充最后的信息', + isUser: false, + timestamp: DateTime.now(), + ), + ); + } + break; + + case FeedbackState.chatting_4: + if (currentRound >= 4) { + _completeConversation(); + } + break; + + default: + break; + } }); } + /// 完成对话,进入completed状态 + void _completeConversation() { + setState(() { + _currentState = FeedbackState.completed; + _session = _session?.markAsCompleted(); + }); + + _addBotMessage( + '🎉 对话已完成!感谢您的详细反馈。\n\n' + '请您确认信息后点击「📤 发送反馈」按钮。', + ); + + // 添加完成提示到会话 + _session = _session?.addMessage( + FeedbackMessage( + text: '对话已完成,等待用户提交', + isUser: false, + timestamp: DateTime.now(), + ), + ); + } + + // ═══════════════════════════════════════════════════════════════ + // 邮件发送逻辑 + // ═══════════════════════════════════════════════════════════════ + + /// 提交反馈(发送邮件) + Future _submitFeedback() async { + if (_session == null) return; + + // 验证邮箱格式(如果填写了) + final email = _emailCtrl.text.trim(); + if (email.isNotEmpty && !_isValidEmail(email)) { + _showError('请输入有效的邮箱地址'); + return; + } + + // 更新会话中的用户邮箱 + _session = _session!.withUserEmail(email.isEmpty ? null : email); + + setState(() { + _isSending = true; + _errorMessage = null; + _currentState = FeedbackState.sending; + }); + + try { + // 检查是否可以提交 + final limitResult = await FeedbackStorageService().canSubmitFeedback(); + if (!limitResult.allowed) { + _showError(limitResult.toString()); + setState(() { + _isSending = false; + _currentState = FeedbackState.completed; // 返回completed状态以便重试 + }); + return; + } + + // 发送邮件 + final result = await EmailService.sendFeedbackEmails(session: _session!); + + if (result.success) { + // 保存到本地存储 + await FeedbackStorageService().saveSession(_session!.markAsSent()); + + // 记录提交 + await FeedbackStorageService().recordSubmission(); + + setState(() { + _isSending = false; + _currentState = FeedbackState.sentSuccess; + }); + + _showSuccessDialog(result); + } else { + // 保存失败的会话 + await FeedbackStorageService().saveSession(_session!.markAsFailed()); + + setState(() { + _isSending = false; + _currentState = FeedbackState.sentFailed; + _errorMessage = result.errorMessage ?? '发送失败,请重试'; + }); + + _showError(_errorMessage!); + } + } catch (e) { + setState(() { + _isSending = false; + _currentState = FeedbackState.sentFailed; + _errorMessage = e.toString(); + }); + _showError(_errorMessage!); + } + } + + /// 显示成功对话框 + void _showSuccessDialog(FeedbackSendResult result) { + showCupertinoDialog( + context: context, + builder: (ctx) => CupertinoAlertDialog( + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + CupertinoIcons.check_mark_circled, + color: CupertinoColors.systemGreen, + size: 24, + ), + SizedBox(width: 8), + Text('✅ 发送成功'), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 12), + Text('感谢您的反馈!我们会尽快处理。', style: TextStyle(fontSize: 15)), + if (_session?.userEmail != null && _session!.userEmail!.isNotEmpty) + Padding( + padding: EdgeInsets.only(top: 8), + child: Text( + '📬 已向 ${_session!.userEmail} 发送确认邮件', + style: TextStyle( + fontSize: 13, + color: CupertinoColors.secondaryLabel, + ), + ), + ), + SizedBox(height: 8), + Text( + result.toString(), + style: TextStyle( + fontSize: 12, + color: CupertinoColors.tertiaryLabel, + ), + ), + ], + ), + actions: [ + CupertinoDialogAction( + child: Text('返回'), + onPressed: () { + Navigator.pop(ctx); + Navigator.pop(context); // 返回上一页 + }, + ), + CupertinoDialogAction( + isDefaultAction: true, + child: Text('提交新反馈'), + onPressed: () { + Navigator.pop(ctx); + _resetAndStartNew(); + }, + ), + ], + ), + ); + } + + /// 显示错误信息 + void _showError(String message) { + showCupertinoDialog( + context: context, + builder: (ctx) => CupertinoAlertDialog( + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + CupertinoIcons.xmark_circle, + color: CupertinoColors.systemRed, + size: 24, + ), + SizedBox(width: 8), + Text('❌ 发送失败'), + ], + ), + content: Text(message, style: TextStyle(fontSize: 15)), + actions: [ + CupertinoDialogAction( + child: Text('返回修改'), + onPressed: () { + Navigator.pop(ctx); + setState(() => _currentState = FeedbackState.completed); + }, + ), + CupertinoDialogAction( + isDefaultAction: true, + child: Text('重试'), + onPressed: () { + Navigator.pop(ctx); + _submitFeedback(); + }, + ), + ], + ), + ); + } + + /// 重置并开始新的反馈流程 + void _resetAndStartNew() { + setState(() { + _messages.clear(); + _session = null; + _emailCtrl.clear(); + _errorMessage = null; + _isSending = false; + }); + _initConversation(); + } + + /// 验证邮箱格式 + bool _isValidEmail(String email) { + return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email); + } + + /// 显示功能说明对话框 + void _showInfoDialog() { + final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; + final textColor = isDark ? DarkDesignTokens.text1 : DesignTokens.text1; + final secondaryColor = isDark ? DarkDesignTokens.text2 : DesignTokens.text2; + + showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + CupertinoIcons.question_circle_fill, + color: DesignTokens.primary, + size: 22, + ), + const SizedBox(width: 8), + const Text('📋 使用说明'), + ], + ), + content: Padding( + padding: const EdgeInsets.only(top: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildInfoItem('🎯', '反馈用途', textColor, secondaryColor), + const SizedBox(height: 8), + Text( + '您的反馈将帮助我们改进小妈厨房 App,提供更好的服务体验。', + style: TextStyle(color: secondaryColor, fontSize: 13), + ), + const SizedBox(height: 16), + _buildInfoItem('💬', '对话机制', textColor, secondaryColor), + const SizedBox(height: 8), + Text( + '支持 5 轮对话交流,可详细描述问题或建议,我们会认真处理每一条反馈。', + style: TextStyle(color: secondaryColor, fontSize: 13), + ), + const SizedBox(height: 16), + _buildInfoItem('📧', '数据发送', textColor, secondaryColor), + const SizedBox(height: 8), + Text( + '对话记录将发送至服务器邮箱,包含:\n' + '• 反馈类型与内容\n' + '• 设备信息(型号、系统版本)\n' + '• 日志数据(用于问题定位)\n\n' + '🔒 数据仅用于产品优化,不会泄露给第三方。', + style: TextStyle(color: secondaryColor, fontSize: 13), + ), + const SizedBox(height: 16), + _buildInfoItem('🔒', '隐私保护', textColor, secondaryColor), + const SizedBox(height: 8), + Text( + '如选择填写邮箱,我们将发送自动回复确认收到您的反馈,感谢您的支持!', + style: TextStyle(color: secondaryColor, fontSize: 13), + ), + ], + ), + ), + actions: [ + CupertinoDialogAction( + isDefaultAction: true, + onPressed: () => Navigator.pop(context), + child: const Text('知道了'), + ), + ], + ), + ); + } + + Widget _buildInfoItem( + String emoji, + String title, + Color titleColor, + Color contentColor, + ) { + return Row( + children: [ + Text(emoji, style: const TextStyle(fontSize: 16)), + const SizedBox(width: 8), + Text( + title, + style: TextStyle( + color: titleColor, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ); + } + + // ═══════════════════════════════════════════════════════════════ + // UI构建 + // ═══════════════════════════════════════════════════════════════ + @override Widget build(BuildContext context) { final isDark = CupertinoTheme.brightnessOf(context) == Brightness.dark; @@ -129,11 +572,21 @@ class _FeedbackPageState extends State { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( middle: Text( - '\u{1F4E8} 意见反馈', + '📝 意见反馈', style: TextStyle( color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, ), ), + trailing: CupertinoButton( + padding: EdgeInsets.zero, + minSize: 36, + onPressed: _showInfoDialog, + child: Icon( + CupertinoIcons.info_circle, + color: isDark ? DarkDesignTokens.primary : DesignTokens.primary, + size: 22, + ), + ), backgroundColor: isDark ? DarkDesignTokens.background.withValues(alpha: 0.85) : DesignTokens.background.withValues(alpha: 0.85), @@ -143,14 +596,41 @@ class _FeedbackPageState extends State { child: Column( children: [ Expanded(child: _buildMessageList(isDark)), - if (!_typeSelected) _buildTypeQuickReplies(isDark), - _buildInputBar(isDark), + + // 根据状态显示不同的底部组件 + ..._buildBottomSection(isDark), ], ), ), ); } + /// 构建底部区域(根据状态动态切换) + List _buildBottomSection(bool isDark) { + switch (_currentState) { + case FeedbackState.idle: + case FeedbackState.selecting: + return [_buildTypeQuickReplies(isDark)]; + + case FeedbackState.chatting_1: + case FeedbackState.chatting_2: + case FeedbackState.chatting_3: + case FeedbackState.chatting_4: + return [_buildInputBar(isDark)]; + + case FeedbackState.completed: + case FeedbackState.sentFailed: + return [_buildSubmitPanel(isDark)]; + + case FeedbackState.sending: + return [_buildSendingIndicator(isDark)]; + + case FeedbackState.sentSuccess: + return []; // 成功后会弹出对话框,不需要底部组件 + } + } + + /// 构建消息列表 Widget _buildMessageList(bool isDark) { return ListView.builder( controller: _scrollCtrl, @@ -166,6 +646,7 @@ class _FeedbackPageState extends State { ); } + /// 构建消息气泡 Widget _buildBubble(_ChatMessage msg, bool isDark) { return Padding( padding: const EdgeInsets.only(bottom: DesignTokens.space3), @@ -237,14 +718,14 @@ class _FeedbackPageState extends State { ); } + /// 构建头像 Widget _buildAvatar(bool isDark, bool isUser) { return Container( width: 32, height: 32, decoration: BoxDecoration( color: isUser - ? (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.15) + ? (DesignTokens.dynamicPrimary).withValues(alpha: 0.15) : (isDark ? DarkDesignTokens.card : DesignTokens.card), borderRadius: DesignTokens.borderRadiusMd, border: Border.all( @@ -253,14 +734,12 @@ class _FeedbackPageState extends State { ), ), child: Center( - child: Text( - isUser ? '\u{1F464}' : '\u{1F916}', - style: const TextStyle(fontSize: 16), - ), + child: Text(isUser ? '👤' : '🤖', style: const TextStyle(fontSize: 16)), ), ); } + /// 构建类型快捷回复按钮 Widget _buildTypeQuickReplies(bool isDark) { return Container( padding: const EdgeInsets.symmetric( @@ -281,14 +760,12 @@ class _FeedbackPageState extends State { vertical: DesignTokens.space2, ), decoration: BoxDecoration( - color: - (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.1), + color: (DesignTokens.dynamicPrimary).withValues(alpha: 0.1), borderRadius: DesignTokens.borderRadiusLg, border: Border.all( - color: - (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.3), + color: (DesignTokens.dynamicPrimary).withValues( + alpha: 0.3, + ), ), ), child: Row( @@ -315,7 +792,11 @@ class _FeedbackPageState extends State { ); } + /// 构建输入栏 Widget _buildInputBar(bool isDark) { + final currentRound = _session?.currentRound ?? 0; + final remainingRounds = 4 - currentRound; + return Container( padding: const EdgeInsets.symmetric( horizontal: DesignTokens.space3, @@ -332,44 +813,317 @@ class _FeedbackPageState extends State { ), child: SafeArea( top: false, - child: Row( + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: CupertinoTextField( - controller: _ctrl, - placeholder: _typeSelected ? '输入您的反馈…' : '请先选择反馈类型', - enabled: _typeSelected, - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 10, - ), - decoration: BoxDecoration( - color: isDark - ? DarkDesignTokens.background - : DesignTokens.background, - borderRadius: DesignTokens.borderRadiusLg, - ), - style: TextStyle( - fontSize: DesignTokens.fontMd, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - onSubmitted: (_) => _send(), + // 进度指示器 + Container( + margin: EdgeInsets.only(bottom: DesignTokens.space2), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '💬 第${currentRound + 1}/4轮 ', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: DesignTokens.dynamicPrimary, + fontWeight: FontWeight.w600, + ), + ), + Text( + '(剩余$remainingRounds轮)', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + ], ), ), - SizedBox(width: 8), - CupertinoButton( - padding: EdgeInsets.zero, - minimumSize: Size(36, 36), - borderRadius: DesignTokens.borderRadiusLg, - color: _typeSelected && _ctrl.text.trim().isNotEmpty - ? (DesignTokens.dynamicPrimary) - : (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) - .withValues(alpha: 0.3), - onPressed: _typeSelected ? _send : null, - child: Icon( - CupertinoIcons.arrow_up, - size: 18, - color: CupertinoColors.white, + + // 输入框和发送按钮 + Row( + children: [ + Expanded( + child: CupertinoTextField( + controller: _ctrl, + placeholder: remainingRounds > 0 + ? '输入您的反馈内容...' + : '已达到上限,请点击下方提交', + enabled: remainingRounds > 0, + maxLines: 3, + minLines: 1, + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 10, + ), + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + borderRadius: DesignTokens.borderRadiusLg, + ), + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + onSubmitted: (_) => _send(), + ), + ), + SizedBox(width: 8), + CupertinoButton( + padding: EdgeInsets.zero, + minimumSize: Size(36, 36), + borderRadius: DesignTokens.borderRadiusLg, + color: remainingRounds > 0 && _ctrl.text.trim().isNotEmpty + ? (DesignTokens.dynamicPrimary) + : (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.3), + onPressed: remainingRounds > 0 ? _send : null, + child: Icon( + CupertinoIcons.arrow_up, + size: 18, + color: CupertinoColors.white, + ), + ), + ], + ), + ], + ), + ), + ); + } + + /// 构建提交面板(完成后显示) + Widget _buildSubmitPanel(bool isDark) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + border: Border( + top: BorderSide( + color: (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.15), + ), + ), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 反馈摘要卡片 + Container( + width: double.infinity, + padding: EdgeInsets.all(DesignTokens.space3), + decoration: BoxDecoration( + color: (DesignTokens.dynamicPrimary).withValues(alpha: 0.08), + borderRadius: DesignTokens.borderRadiusLg, + border: Border.all( + color: (DesignTokens.dynamicPrimary).withValues(alpha: 0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + CupertinoIcons.doc_text, + size: 18, + color: DesignTokens.dynamicPrimary, + ), + SizedBox(width: 6), + Text( + '📋 反馈摘要', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: DesignTokens.dynamicPrimary, + ), + ), + ], + ), + SizedBox(height: DesignTokens.space2), + _buildSummaryRow( + '类型', + '${_session?.type.emoji} ${_session?.type.label}', + ), + _buildSummaryRow('轮次', '5/5 (已完成)'), + _buildSummaryRow( + '消息数', + '${_session?.userMessages.length ?? 0} 条', + ), + ], + ), + ), + + SizedBox(height: DesignTokens.space3), + + // 邮箱输入 + Text( + '📧 您的邮箱(选填)', + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + SizedBox(height: DesignTokens.space2), + CupertinoTextField( + controller: _emailCtrl, + placeholder: 'example@email.com', + keyboardType: TextInputType.emailAddress, + clearButtonMode: OverlayVisibilityMode.editing, + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12), + decoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.background + : DesignTokens.background, + borderRadius: DesignTokens.borderRadiusLg, + ), + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + ), + SizedBox(height: DesignTokens.space1), + Text( + '💡 填写后我们将发送确认邮件给您', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + ), + ), + + SizedBox(height: DesignTokens.space3), + + // 错误提示 + if (_errorMessage != null && + _currentState == FeedbackState.sentFailed) + Container( + margin: EdgeInsets.only(bottom: DesignTokens.space2), + padding: EdgeInsets.all(DesignTokens.space2), + decoration: BoxDecoration( + color: CupertinoColors.systemRed.withValues(alpha: 0.1), + borderRadius: DesignTokens.borderRadiusMd, + ), + child: Row( + children: [ + Icon( + CupertinoIcons.exclamationmark_circle, + size: 16, + color: CupertinoColors.systemRed, + ), + SizedBox(width: 6), + Expanded( + child: Text( + _errorMessage!, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: CupertinoColors.systemRed, + ), + ), + ), + ], + ), + ), + + // 提交按钮 + SizedBox( + width: double.infinity, + child: CupertinoButton( + padding: EdgeInsets.symmetric(vertical: 14), + borderRadius: DesignTokens.borderRadiusLg, + color: DesignTokens.dynamicPrimary, + onPressed: _isSending ? null : _submitFeedback, + child: _isSending + ? CupertinoActivityIndicator(color: CupertinoColors.white) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(CupertinoIcons.paperplane, size: 18), + SizedBox(width: 8), + Text( + '📤 发送反馈', + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + /// 构建摘要行 + Widget _buildSummaryRow(String label, String value) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + label, + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: CupertinoColors.secondaryLabel, + ), + ), + Text( + value, + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ); + } + + /// 构建发送中指示器 + Widget _buildSendingIndicator(bool isDark) { + return Container( + padding: const EdgeInsets.all(DesignTokens.space4), + decoration: BoxDecoration( + color: isDark ? DarkDesignTokens.card : DesignTokens.card, + border: Border( + top: BorderSide( + color: (isDark ? DarkDesignTokens.text3 : DesignTokens.text3) + .withValues(alpha: 0.15), + ), + ), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoActivityIndicator(radius: 16), + SizedBox(height: DesignTokens.space2), + Text( + '正在发送反馈...', + style: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + ), + SizedBox(height: DesignTokens.space1), + Text( + '请稍候,正在将您的反馈发送至服务器', + style: TextStyle( + fontSize: DesignTokens.fontSm, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, ), ), ], @@ -378,3 +1132,16 @@ class _FeedbackPageState extends State { ); } } + +/// 内部消息模型(用于UI展示) +class _ChatMessage { + final String text; + final bool isUser; + final FeedbackType? feedbackType; + + const _ChatMessage({ + required this.text, + required this.isUser, + this.feedbackType, + }); +} diff --git a/lib/src/pages/profile/tools/permission_page.dart b/lib/src/pages/profile/tools/permission_page.dart index 5f1f184..1d0e120 100644 --- a/lib/src/pages/profile/tools/permission_page.dart +++ b/lib/src/pages/profile/tools/permission_page.dart @@ -132,6 +132,7 @@ class PermissionPage extends StatelessWidget { 'title': '设备标识', 'desc': '获取设备唯一标识', }, + {'icon': CupertinoIcons.folder, 'title': '安全文件访问', 'desc': '读写用户文件'}, ]; return permissions @@ -238,6 +239,7 @@ class PermissionPage extends StatelessWidget { // {'title': '播放声音', 'desc': '用于操作提示音,提升用户体验。'}, {'title': '分享能力', 'desc': '用于分享菜谱内容到社交媒体平台。'}, {'title': '设备标识', 'desc': '用于唯一标识设备,确保用户数据安全。'}, + {'title': '安全文件访问', 'desc': '用于读写文件,管理用户导入/导出的文件。'}, ]; return descriptions @@ -351,7 +353,7 @@ class PermissionPage extends StatelessWidget { const SizedBox(height: DesignTokens.space3), _buildSandboxItem('📱 沙盒隔离', '软件在独立沙盒环境中运行,无法访问系统其他应用数据', isDark), const SizedBox(height: DesignTokens.space2), - _buildSandboxItem('📄 无文件创建', '不会在设备上创建额外文件,所有数据通过网络获取', isDark), + _buildSandboxItem('📄 无文件创建', '所有文件导出均需用户手动操作,不会在设备上创建额外文件', isDark), const SizedBox(height: DesignTokens.space2), _buildSandboxItem('🔒 权限透明', '仅使用必要权限,此类权限均为基础权限', isDark), const SizedBox(height: DesignTokens.space2), diff --git a/lib/src/pages/tools/ingredient/cooking_tip_detail_page.dart b/lib/src/pages/tools/ingredient/cooking_tip_detail_page.dart index 80e2756..916bcd7 100644 --- a/lib/src/pages/tools/ingredient/cooking_tip_detail_page.dart +++ b/lib/src/pages/tools/ingredient/cooking_tip_detail_page.dart @@ -4,6 +4,7 @@ * 作用: 显示 Markdown 格式的烹饪技巧内容 * 创建: 2026-04-13 * 更新: 2026-04-13 从本地 assets 加载内容 + * 更新: 2026-04-20 修复布局溢出问题,添加 SingleChildScrollView + shrinkWrap 处理长内容 */ import 'package:flutter/cupertino.dart'; @@ -91,7 +92,10 @@ class _CookingTipDetailPageState extends State { return CustomScrollView( slivers: [ SliverToBoxAdapter(child: _buildHeader(isDark)), - SliverToBoxAdapter(child: _buildMarkdownContent(isDark)), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: DesignTokens.space3), + sliver: SliverToBoxAdapter(child: _buildMarkdownContent(isDark)), + ), SliverToBoxAdapter(child: _buildFooter(isDark)), ], ); @@ -149,88 +153,92 @@ class _CookingTipDetailPageState extends State { Widget _buildMarkdownContent(bool isDark) { return Container( - margin: const EdgeInsets.symmetric(horizontal: DesignTokens.space3), padding: const EdgeInsets.all(DesignTokens.space3), decoration: BoxDecoration( color: isDark ? DarkDesignTokens.card : DesignTokens.card, borderRadius: BorderRadius.circular(DesignTokens.radiusMd), ), - child: MarkdownBody( - data: _content!, - selectable: true, - styleSheet: MarkdownStyleSheet( - h1: TextStyle( - fontSize: DesignTokens.fontXl, - fontWeight: FontWeight.bold, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - h2: TextStyle( - fontSize: DesignTokens.fontLg, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - h3: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - p: TextStyle( - fontSize: DesignTokens.fontMd, - color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, - height: 1.6, - ), - listBullet: TextStyle(color: DesignTokens.dynamicPrimary), - code: TextStyle( - fontSize: DesignTokens.fontSm, - backgroundColor: isDark - ? DarkDesignTokens.text3.withValues(alpha: 0.2) - : DesignTokens.text3.withValues(alpha: 0.1), - color: DesignTokens.dynamicPrimary, - ), - codeblockDecoration: BoxDecoration( - color: isDark - ? DarkDesignTokens.text3.withValues(alpha: 0.1) - : DesignTokens.text3.withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(DesignTokens.radiusSm), - ), - blockquote: TextStyle( - color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, - fontStyle: FontStyle.italic, - ), - blockquoteDecoration: BoxDecoration( - border: Border( - left: BorderSide(color: DesignTokens.dynamicPrimary, width: 4), + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: MarkdownBody( + data: _content!, + selectable: true, + shrinkWrap: true, + styleSheet: MarkdownStyleSheet( + h1: TextStyle( + fontSize: DesignTokens.fontXl, + fontWeight: FontWeight.bold, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, ), - ), - tableHead: TextStyle( - fontWeight: FontWeight.w600, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - tableBody: TextStyle( - color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, - ), - tableBorder: TableBorder.all( - color: isDark - ? DarkDesignTokens.text3.withValues(alpha: 0.3) - : DesignTokens.text3.withValues(alpha: 0.2), - borderRadius: BorderRadius.circular(DesignTokens.radiusSm), - ), - horizontalRuleDecoration: BoxDecoration( - border: Border( - top: BorderSide( - color: isDark - ? DarkDesignTokens.text3.withValues(alpha: 0.3) - : DesignTokens.text3.withValues(alpha: 0.2), - width: 1, + h2: TextStyle( + fontSize: DesignTokens.fontLg, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + h3: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + p: TextStyle( + fontSize: DesignTokens.fontMd, + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + height: 1.6, + ), + listBullet: TextStyle(color: DesignTokens.dynamicPrimary), + code: TextStyle( + fontSize: DesignTokens.fontSm, + backgroundColor: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.2) + : DesignTokens.text3.withValues(alpha: 0.1), + color: DesignTokens.dynamicPrimary, + ), + codeblockDecoration: BoxDecoration( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.1) + : DesignTokens.text3.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + codeblockPadding: const EdgeInsets.all(DesignTokens.space3), + blockquote: TextStyle( + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + fontStyle: FontStyle.italic, + ), + blockquoteDecoration: BoxDecoration( + border: Border( + left: BorderSide(color: DesignTokens.dynamicPrimary, width: 4), + ), + ), + tableHead: TextStyle( + fontWeight: FontWeight.w600, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + tableBody: TextStyle( + color: isDark ? DarkDesignTokens.text2 : DesignTokens.text2, + ), + tableBorder: TableBorder.all( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.3) + : DesignTokens.text3.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(DesignTokens.radiusSm), + ), + horizontalRuleDecoration: BoxDecoration( + border: Border( + top: BorderSide( + color: isDark + ? DarkDesignTokens.text3.withValues(alpha: 0.3) + : DesignTokens.text3.withValues(alpha: 0.2), + width: 1, + ), ), ), ), + onTapLink: (text, href, title) { + if (href != null) { + debugPrint('Link tapped: $href'); + } + }, ), - onTapLink: (text, href, title) { - if (href != null) { - debugPrint('Link tapped: $href'); - } - }, ), ); } diff --git a/lib/src/pages/tools/ingredient/cooking_tips_list_page.dart b/lib/src/pages/tools/ingredient/cooking_tips_list_page.dart index 7b74853..0f7e97f 100644 --- a/lib/src/pages/tools/ingredient/cooking_tips_list_page.dart +++ b/lib/src/pages/tools/ingredient/cooking_tips_list_page.dart @@ -1,9 +1,10 @@ -/* +/* * 文件: cooking_tips_list_page.dart * 名称: 烹饪技巧列表页面 * 作用: 显示烹饪技巧分类列表,支持点击查看详情 * 创建: 2026-04-13 - * 更新: 2026-04-13 初始创建,数据来源于 HowToCook 项目 + * 更新: 2026-04-20 修复 TipCard Column 溢出6.8px,改用 mainAxisSize.min + SizedBox 替代 Spacer + * 更新: 2026-04-20 仅 what_to_eat 卡片去掉 emoji 用纯文字 Row,其余保留 Column+emoji 布局 */ import 'dart:convert'; @@ -275,44 +276,78 @@ class _CookingTipsListPageState extends State { } Widget _buildTipCard(CookingTipModel tip, bool isDark) { + final isWhatToEat = tip.id == 'what_to_eat'; + return GestureDetector( onTap: () => Get.to(() => CookingTipDetailPage(tip: tip)), child: GlassContainer( borderRadius: DesignTokens.radiusMd, child: Container( padding: const EdgeInsets.all(DesignTokens.space3), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text(tip.icon, style: const TextStyle(fontSize: 24)), - const Spacer(), - Icon( - CupertinoIcons.chevron_right, - color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, - size: 16, - ), - ], - ), - const Spacer(), - Text( - tip.name, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w500, - color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), + child: isWhatToEat + ? _buildCompactTipCard(tip, isDark) + : _buildStandardTipCard(tip, isDark), ), ), ); } + Widget _buildCompactTipCard(CookingTipModel tip, bool isDark) { + return Row( + children: [ + Expanded( + child: Text( + tip.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: DesignTokens.space2), + Icon( + CupertinoIcons.chevron_right, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + size: 16, + ), + ], + ); + } + + Widget _buildStandardTipCard(CookingTipModel tip, bool isDark) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Text(tip.icon, style: const TextStyle(fontSize: 24)), + const Spacer(), + Icon( + CupertinoIcons.chevron_right, + color: isDark ? DarkDesignTokens.text3 : DesignTokens.text3, + size: 16, + ), + ], + ), + const SizedBox(height: DesignTokens.space2), + Text( + tip.name, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w500, + color: isDark ? DarkDesignTokens.text1 : DesignTokens.text1, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ); + } + Widget _buildSearchResults(bool isDark) { final results = _getFilteredTips(); diff --git a/lib/src/services/api/api_service.dart b/lib/src/services/api/api_service.dart index e583638..46aa6e5 100644 --- a/lib/src/services/api/api_service.dart +++ b/lib/src/services/api/api_service.dart @@ -1,10 +1,9 @@ // 2026-04-09 | ApiService | HTTP客户端服务 | Web端跳过文件缓存和connectivity检查 // 2026-04-10 | API v2.0.0: 新增 _format/_stale/_refresh/_pretty 参数支持 // 2026-04-11 | 优化: 增强日志拦截器、添加重试机制、统一离线检查、缓存数据解析修复 -// 2026-04-12 | Web端CORS修复: 添加CORS代理支持、优化Web端错误处理 // 2026-04-15 | 修复ANR: connectTimeout从15s缩短到5s,防止DNS解析卡死主线程 // 2026-04-15 | 新增DNS预检机制: 启动时在Isolate中预解析DNS,失败则跳过所有网络请求 -// 2026-04-17 | Web端修复v2: 优先直连请求(10s超时),CORS代理降级为备用方案 +// 2026-04-21 | 移除CORS代理,Web端全部直连 import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -19,8 +18,6 @@ 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'; -//web端CORS修复: 添加CORS代理支持 - class ApiService { static final ApiService _instance = ApiService._internal(); factory ApiService() => _instance; @@ -36,8 +33,6 @@ class ApiService { int _activeRequestCount = 0; final List> _requestQueue = []; - static const String _corsProxy = 'https://corsproxy.io/?'; - bool _dnsReachable = true; bool _dnsChecked = false; Completer? _dnsReadyCompleter; @@ -141,11 +136,6 @@ class ApiService { return fullUrl; } - String _buildProxyUrl(String path, [Map? queryParameters]) { - final directUrl = _buildUrl(path, queryParameters); - return '$_corsProxy${Uri.encodeComponent(directUrl)}'; - } - ApiService._internal() { final options = BaseOptions( baseUrl: kIsWeb ? '' : ApiConfig.baseUrl, @@ -165,7 +155,7 @@ class ApiService { _dio.options.extra = {'withCredentials': false}; _dio.interceptors.add(_buildLogInterceptor()); LoggerService().info( - 'ApiService: Web mode - direct request with CORS proxy fallback', + 'ApiService: Web mode - direct connection', ); } else { _initCacheAsync(); @@ -331,23 +321,6 @@ class ApiService { return response; } on DioException catch (e) { - // Web端直连失败时,尝试CORS代理 - if (kIsWeb && _shouldTryProxy(e)) { - debugPrint('ApiService: 直连失败,尝试CORS代理...'); - try { - final proxyUrl = _buildProxyUrl(path, queryParameters); - final proxyResponse = await _executeWithRetry( - () => _dio.get( - proxyUrl, - queryParameters: Map.from(queryParameters ?? {}), - options: Options(headers: {'Accept': 'application/json'}), - ), - ); - return proxyResponse; - } catch (proxyError) { - debugPrint('ApiService: CORS代理也失败: $proxyError'); - } - } final cached = await _tryGetCache(path, queryParameters); if (cached != null) return cached; throw _convertDioException(e); @@ -473,43 +446,6 @@ class ApiService { try { return await _executeWithRetry(request); } on DioException catch (e) { - if (kIsWeb && path != null && _shouldTryProxy(e)) { - debugPrint('ApiService: 直连失败($method),尝试CORS代理...'); - final proxyUrl = _buildProxyUrl(path, queryParameters); - switch (method) { - case 'post': - return await _executeWithRetry( - () => _dio.post( - proxyUrl, - data: data, - options: Options(headers: {'Accept': 'application/json'}), - ), - ); - case 'put': - return await _executeWithRetry( - () => _dio.put( - proxyUrl, - data: data, - options: Options(headers: {'Accept': 'application/json'}), - ), - ); - case 'delete': - return await _executeWithRetry( - () => _dio.delete( - proxyUrl, - options: Options(headers: {'Accept': 'application/json'}), - ), - ); - default: - return await _executeWithRetry( - () => _dio.get( - proxyUrl, - queryParameters: queryParameters, - options: Options(headers: {'Accept': 'application/json'}), - ), - ); - } - } rethrow; } } on DioException catch (e) { @@ -578,22 +514,6 @@ class ApiService { return null; } - bool _shouldTryProxy(DioException e) { - if (e.type == DioExceptionType.connectionError) return true; - if (e.type == DioExceptionType.connectionTimeout) return true; - if (e.type == DioExceptionType.unknown && - e.message?.contains('onerror') == true) { - return true; - } - final response = e.response; - if (response != null) { - final statusCode = response.statusCode; - if (statusCode == 0) return true; - if (statusCode != null && statusCode >= 500) return true; - } - return false; - } - ApiException _convertDioException(DioException e) { switch (e.type) { case DioExceptionType.connectionTimeout: diff --git a/lib/src/services/data/business/email_service.dart b/lib/src/services/data/business/email_service.dart index be118d1..fb89c46 100644 --- a/lib/src/services/data/business/email_service.dart +++ b/lib/src/services/data/business/email_service.dart @@ -1,22 +1,15 @@ // 2026-04-14 | email_service.dart | 邮件发送服务 | 通过SMTP发送菜谱详情到用户邮箱 // 2026-04-14 | 初始创建,基于mailer库实现SMTP邮件发送 -// 2026-04-14 | 新增多线路支持:官方线路1(mboxhosting)/线路2(QQ邮箱)/自定义SMTP -// 2026-04-15 | 发送成功/失败后自动记录到 EmailHistoryController -// 2026-04-17 | SMTP凭证迁移至.env,移除硬编码敏感信息防泄露 -// 2026-04-19 | 丰富HTML邮件模板,底部显示设备UUID+发件IP,记录含UUID/IP -// 2026-04-19 | 移除flutter_dotenv依赖,SMTP凭证改为直接配置 -// 2026-04-19 | 修复HandshakeException:添加ignoreBadCertificate+587端口备选+自动重试 -// 2026-04-19 | 重构备选机制:SmtpRoute支持多级fallback,自定义SMTP也支持备选端口 -// 2026-04-19 | 修复fallback配置:移除587/SSL(端口协议不匹配)+移除25/明文(ISP封禁) -// 2026-04-19 | 修复allowInsecure逻辑:STARTTLS必须要求加密升级成功+添加发送超时 -// 2026-04-19 | 修复connection.dart:SSL握手失败后延迟升级也失败则回退明文+STARTTLS + // 2026-04-19 | 新增VPN/代理检测:DNS解析到198.18.x等代理IP时警告+错误提示建议关闭VPN +// 2026-04-21 | 新增反馈邮件功能:支持发送用户反馈到管理员+自动回复给用户 import 'dart:async'; import 'dart:io'; import 'package:get/get.dart'; import 'package:mom_kitchen/src/models/recipe/recipe_model.dart'; import 'package:mom_kitchen/src/models/data/record/email_record_model.dart'; +import 'package:mom_kitchen/src/models/feedback_model.dart'; import 'package:mom_kitchen/src/controllers/user/email_history_controller.dart'; import 'package:mom_kitchen/src/services/core/user_service.dart'; import 'package:mom_kitchen/src/services/log/logger_service.dart'; @@ -828,7 +821,7 @@ class EmailService { 'Device UUID 和 IP 仅用于安全追溯,不会泄露给第三方。

' '⚠️ 如您未订阅此邮件或认为此邮件构成骚扰,请发送投诉至 ' 'ad@avefs.com,' - '我们将在24小时内处理。' + '我们将在48小时内处理。' '', ); buffer.writeln(''); @@ -836,4 +829,439 @@ class EmailService { buffer.writeln(''); return buffer.toString(); } + + // ═══════════════════════════════════════════════════════════════ + // 反馈邮件功能(2026-04-21 新增) + // ═══════════════════════════════════════════════════════════════ + + /// 发送反馈邮件(双发:管理员 + 用户自动回复) + /// [session] 反馈会话数据(包含对话记录、设备信息等) + static Future sendFeedbackEmails({ + required FeedbackSession session, + }) async { + final route = presetRoutes[0]; // 使用官方线路1 + + LoggerService().info( + '[反馈邮件] 开始发送: sessionId=${session.id}, type=${session.type.label}', + ); + + bool adminSent = false; + bool userSent = true; // 默认成功,如果用户没填邮箱则不需要发 + + try { + // 1. 发送到管理员 (ad@avefs.com) + adminSent = await _sendFeedbackToAdmin(session: session, route: route); + + if (!adminSent) { + LoggerService().error('[反馈邮件] 发送给管理员失败'); + return FeedbackSendResult( + success: false, + adminSent: false, + userSent: false, + errorMessage: '发送到管理员邮箱失败', + ); + } + + LoggerService().info('[反馈邮件] ✅ 管理员邮件发送成功'); + + // 2. 如果用户提供了邮箱,发送自动回复 + if (session.userEmail != null && + session.userEmail!.isNotEmpty && + _isValidEmail(session.userEmail!)) { + userSent = await _sendAutoReplyToUser(session: session, route: route); + + if (userSent) { + LoggerService().info('[反馈邮件] ✅ 自动回复已发送至 ${session.userEmail}'); + } else { + LoggerService().warning('[反馈邮件] ⚠️ 自动回复发送失败 (${session.userEmail})'); + } + } + + return FeedbackSendResult( + success: true, + adminSent: adminSent, + userSent: userSent, + ); + } catch (e) { + LoggerService().error('[反馈邮件] ❌ 发送异常: $e'); + return FeedbackSendResult( + success: false, + adminSent: adminSent, + userSent: false, + errorMessage: e.toString(), + ); + } + } + + /// 发送反馈给管理员 + static Future _sendFeedbackToAdmin({ + required FeedbackSession session, + required SmtpRoute route, + }) async { + final smtpServer = SmtpServer( + route.host, + port: route.port, + ssl: route.ssl, + ignoreBadCertificate: true, + allowInsecure: false, + username: route.username, + password: route.password, + ); + + final message = Message() + ..from = Address(route.username, '小妈厨房-反馈系统') + ..recipients.add('ad@avefs.com') + ..subject = '📝 [${session.type.label}] 用户反馈 - ${session.deviceInfo.uuid}' + ..html = _buildFeedbackAdminHtml(session); + + try { + final sendReport = await send( + message, + smtpServer, + timeout: const Duration(seconds: 30), + ); + LoggerService().debug('[反馈-管理员] 发送成功: $sendReport'); + return true; + } catch (e) { + LoggerService().error('[反馈-管理员] 发送失败: $e'); + return false; + } + } + + /// 发送自动回复给用户 + static Future _sendAutoReplyToUser({ + required FeedbackSession session, + required SmtpRoute route, + }) async { + final smtpServer = SmtpServer( + route.host, + port: route.port, + ssl: route.ssl, + ignoreBadCertificate: true, + allowInsecure: false, + username: route.username, + password: route.password, + ); + + final message = Message() + ..from = Address(route.username, '小妈厨房') + ..recipients.add(session.userEmail!) + ..subject = '🎉 我们已收到您的反馈 - 小妈厨房' + ..html = _buildFeedbackAutoReplyHtml(session); + + try { + final sendReport = await send( + message, + smtpServer, + timeout: const Duration(seconds: 30), + ); + LoggerService().debug('[反馈-自动回复] 发送成功: $sendReport'); + return true; + } catch (e) { + LoggerService().error('[反馈-自动回复] 发送失败: $e'); + return false; + } + } + + /// 验证邮箱格式 + static bool _isValidEmail(String email) { + return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email); + } + + /// 构建管理员反馈邮件HTML内容 + static String _buildFeedbackAdminHtml(FeedbackSession session) { + final buffer = StringBuffer(); + final device = session.deviceInfo; + + buffer.writeln(''); + buffer.writeln(''); + buffer.writeln( + '', + ); + buffer.writeln(''); + + // 头部 + buffer.writeln('
'); + buffer.writeln('

📝 用户反馈详情

'); + buffer.writeln('

来自小妈厨房App的自动反馈

'); + buffer.writeln('
'); + + buffer.writeln('
'); + + // 用户信息卡片 + buffer.writeln('
'); + buffer.writeln('

👤 用户信息

'); + buffer.writeln('
'); + buffer.writeln(_buildInfoRow('设备UUID', device.uuid)); + buffer.writeln(_buildInfoRow('IP地址', device.ip)); + buffer.writeln(_buildInfoRow('应用版本', 'v${device.appVersion}')); + buffer.writeln( + _buildInfoRow('平台', '${device.platform} ${device.osVersion}'), + ); + buffer.writeln( + _buildInfoRow( + '用户邮箱', + session.userEmail ?? '未提供', + ), + ); + buffer.writeln(_buildInfoRow('反馈编号', session.id)); + buffer.writeln( + _buildInfoRow('提交时间', session.createdAt.toString().substring(0, 19)), + ); + buffer.writeln( + _buildInfoRow('反馈类型', '${session.type.emoji} ${session.type.label}'), + ); + buffer.writeln('
'); + buffer.writeln('
'); + + // 对话记录 + buffer.writeln('
'); + buffer.writeln('

💬 对话记录

'); + buffer.writeln('
'); + + for (var i = 0; i < session.messages.length; i++) { + final msg = session.messages[i]; + final isUser = msg.isUser; + buffer.writeln('
'); + buffer.writeln( + '${isUser ? "👤 用户" : "🤖 系统"}', + ); + buffer.writeln('${_escapeHtml(msg.text)}'); + buffer.writeln( + '${msg.timestamp.toString().substring(11, 19)}', + ); + buffer.writeln('
'); + } + + buffer.writeln('
'); + buffer.writeln('
'); + + // 系统日志 + if (device.logs.isNotEmpty) { + buffer.writeln('
'); + buffer.writeln('

📋 系统日志

'); + buffer.writeln('
${_escapeHtml(device.logs)}
'); + buffer.writeln('
'); + } + + buffer.writeln('
'); + + // 底部 + buffer.writeln(''); + + buffer.writeln(''); + return buffer.toString(); + } + + /// 构建用户自动回复邮件HTML内容 + static String _buildFeedbackAutoReplyHtml(FeedbackSession session) { + final buffer = StringBuffer(); + + buffer.writeln(''); + buffer.writeln(''); + buffer.writeln( + '', + ); + buffer.writeln(''); + + // 成功头部 + buffer.writeln( + '
', + ); + buffer.writeln('

✅ 反馈已收到

'); + buffer.writeln('

感谢您对小妈厨房的支持!

'); + buffer.writeln('
'); + + buffer.writeln('
'); + + // 感谢语 + buffer.writeln('
'); + buffer.writeln( + '

', + ); + buffer.writeln( + '您好!我们已收到您的${session.type.emoji} ${session.type.label}反馈。', + ); + buffer.writeln('我们的团队会尽快查看并处理您的问题或建议。

'); + buffer.writeln('
'); + + // 反馈摘要 + buffer.writeln('
'); + buffer.writeln('

📋 反馈摘要

'); + buffer.writeln('
'); + buffer.writeln(_buildInfoRow('反馈编号', session.id)); + buffer.writeln( + _buildInfoRow('提交时间', session.createdAt.toString().substring(0, 19)), + ); + buffer.writeln( + _buildInfoRow('反馈类型', '${session.type.emoji} ${session.type.label}'), + ); + buffer.writeln('
'); + buffer.writeln('
'); + + // 后续步骤 + buffer.writeln('
'); + buffer.writeln('

📌 后续处理流程

'); + buffer.writeln('
    '); + buffer.writeln( + '
  1. 72小时内审核:我们的团队将优先处理您的反馈
  2. ', + ); + buffer.writeln( + '
  3. Bug修复:如果是技术问题,我们会优先修复并在下个版本更新
  4. ', + ); + buffer.writeln( + '
  5. 功能建议:将被纳入产品路线图评估和规划
  6. ', + ); + buffer.writeln( + '
  7. 持续跟进:重要问题我们会通过邮件与您保持沟通
  8. ', + ); + buffer.writeln('
'); + buffer.writeln('
'); + + // 联系方式 + buffer.writeln('
'); + buffer.writeln('

💬 联系我们

'); + buffer.writeln( + '

', + ); + buffer.writeln('如有紧急问题或需要补充信息,请联系:
'); + buffer.writeln( + '📧 ad@avefs.com', + ); + buffer.writeln('

'); + buffer.writeln('
'); + + buffer.writeln('
'); + + // 底部 + buffer.writeln(''); + + buffer.writeln(''); + return buffer.toString(); + } + + /// 基础邮件样式 + static String _getBaseEmailStyles() { + return ''' +body { font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, sans-serif; + max-width: 640px; margin: 0 auto; padding: 0; background: #F2F2F7; color: #1C1C1E; + -webkit-font-smoothing: antialiased; } +.header { background: linear-gradient(135deg, #007AFF 0%, #5856D6 50%, #AF52DE 100%); + color: white; padding: 32px 24px; border-radius: 16px 16px 0 0; position: relative; overflow: hidden; } +.header h1 { margin: 0 0 8px; font-size: 26px; font-weight: 700; letter-spacing: -0.5px; } +.header .subtitle { margin: 0; opacity: 0.9; font-size: 15px; line-height: 1.5; } +.content { padding: 0 16px 16px; } +.section { background: white; padding: 20px; border-radius: 12px; + margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); } +.section h2 { margin: 0 0 16px; font-size: 17px; font-weight: 600; + color: #1C1C1E; display: flex; align-items: center; gap: 8px; } +.section h2 .icon { font-size: 20px; } +.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } +.info-row { background: #F2F2F7; padding: 10px 12px; border-radius: 8px; } +.info-row .label { font-size: 11px; color: #8E8E93; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px; } +.info-row .value { font-size: 13px; color: #1C1C1E; font-weight: 500; word-break: break-all; } +.chat-history { max-height: 400px; overflow-y: auto; } +.chat-msg { padding: 12px; margin-bottom: 8px; border-radius: 12px; display: flex; flex-direction: column; gap: 4px; } +.chat-msg.bot { background: #F2F2F7; align-items: flex-start; } +.chat-msg.user { background: linear-gradient(135deg, #007AFF15, #5856D615); align-items: flex-start; } +.msg-role { font-size: 11px; font-weight: 600; color: #8E8E93; } +.msg-text { font-size: 14px; line-height: 1.5; color: #1C1C1E; } +.msg-time { font-size: 11px; color: #C7C7CC; align-self: flex-end; } +.logs { background: #1C1C1E; color: #30D158; padding: 16px; border-radius: 8px; + font-family: "SF Mono", Monaco, monospace; font-size: 12px; line-height: 1.6; + overflow-x: auto; white-space: pre-wrap; word-break: break-all; max-height: 300px; overflow-y: auto; } +.tip-box { background: linear-gradient(135deg, #FFF9E6, #FFF3CC); + border-left: 4px solid #FFB800; } +.footer { background: white; padding: 20px 24px; border-radius: 12px; + margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.04); } +.footer-brand { text-align: center; padding-bottom: 16px; + border-bottom: 1px solid #F2F2F7; margin-bottom: 16px; } +.footer-brand .logo { font-size: 28px; margin-bottom: 4px; } +.footer-brand .name { font-size: 16px; font-weight: 600; color: #1C1C1E; } +.footer-brand .slogan { font-size: 12px; color: #8E8E93; margin-top: 2px; } +.footer-disclaimer { text-align: center; padding: 12px 0 0; + font-size: 11px; color: #C7C7CC; line-height: 1.5; } +@media only screen and (max-width: 480px) { + .header { padding: 24px 16px; } .header h1 { font-size: 22px; } + .content { padding: 0 8px 8px; } .section { padding: 16px; } + .info-grid { grid-template-columns: 1fr; } +} +'''; + } + + /// 构建信息行 + static String _buildInfoRow(String label, String value) { + return ''' +
+
$label
+
$value
+
'''; + } + + /// HTML转义 + static String _escapeHtml(String text) { + return text + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + } +} + +/// 反馈发送结果 +class FeedbackSendResult { + final bool success; // 是否整体成功 + final bool adminSent; // 管理员邮件是否发送成功 + final bool userSent; // 用户自动回复是否发送成功 + final String? errorMessage; // 错误信息 + + const FeedbackSendResult({ + required this.success, + required this.adminSent, + required this.userSent, + this.errorMessage, + }); + + @override + String toString() { + if (success) { + var details = []; + if (adminSent) details.add('✅ 管理员邮件已发送'); + if (userSent) details.add('✅ 用户已收到自动回复'); + if (!userSent && details.length == 1) details.add('ℹ️ 用户未填写邮箱,跳过自动回复'); + return details.join('\n'); + } + return '❌ 发送失败: $errorMessage'; + } } diff --git a/lib/src/services/local/feedback_storage_service.dart b/lib/src/services/local/feedback_storage_service.dart new file mode 100644 index 0000000..df15d0c --- /dev/null +++ b/lib/src/services/local/feedback_storage_service.dart @@ -0,0 +1,240 @@ +// 2026-04-21 | feedback_storage_service.dart | 反馈本地存储服务 | 使用shared_preferences管理反馈历史记录和限制 +// 2026-04-21 | 初始创建,支持反馈会话的CRUD、每日限制、冷却时间 + +import 'dart:convert'; +import 'package:flutter/foundation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:mom_kitchen/src/models/feedback_model.dart'; + +/// 反馈本地存储服务 +/// 管理反馈会话的持久化存储、查询、限制逻辑 +class FeedbackStorageService { + FeedbackStorageService._internal(); + static final FeedbackStorageService _instance = FeedbackStorageService._internal(); + factory FeedbackStorageService() => _instance; + + static const String _kFeedbackHistory = 'feedback_history'; // 历史记录键 + static const String _kLastFeedbackTime = 'last_feedback_time'; // 最后提交时间 + static const String _kFeedbackCountToday = 'feedback_count_today'; // 今日次数 + static const String _kLastResetDate = 'last_reset_date'; // 最后重置日期 + + /// 限制配置 + static const int maxPerDay = 3; // 每日最多3次 + static const int cooldownMinutes = 30; // 冷却时间30分钟 + static const int maxHistoryRecords = 50; // 最大保存50条历史记录 + + /// 获取SharedPreferences实例 + Future get _prefs async => + SharedPreferences.getInstance(); + + /// ═══════════════════════════════════════════════════════════════ + /// CRUD 操作 + /// ═══════════════════════════════════════════════════════════════ + + /// 保存反馈会话(新增或更新) + Future saveSession(FeedbackSession session) async { + final prefs = await _prefs; + final history = await getAllSessions(); + + // 查找是否已存在该ID的记录 + final index = history.indexWhere((s) => s.id == session.id); + if (index >= 0) { + history[index] = session; + } else { + history.insert(0, session); // 新记录插入到开头 + } + + // 限制最大数量,移除最旧的记录 + if (history.length > maxHistoryRecords) { + history.removeRange(maxHistoryRecords, history.length); + } + + return await prefs.setString( + _kFeedbackHistory, + jsonEncode(history.map((s) => s.toMap()).toList()), + ); + } + + /// 获取所有反馈会话(按时间倒序) + Future> getAllSessions() async { + final prefs = await _prefs; + final jsonStr = prefs.getString(_kFeedbackHistory); + + if (jsonStr == null || jsonStr.isEmpty) return []; + + try { + final List jsonList = jsonDecode(jsonStr); + return jsonList + .map((json) => FeedbackSession.fromMap(json as Map)) + .toList(); + } catch (e) { + debugPrint('[FeedbackStorage] 解析历史记录失败: $e'); + return []; + } + } + + /// 根据ID获取单个会话 + Future getSession(String id) async { + final sessions = await getAllSessions(); + try { + return sessions.firstWhere((s) => s.id == id); + } catch (e) { + return null; + } + } + + /// 删除指定会话 + Future deleteSession(String id) async { + final prefs = await _prefs; + final history = await getAllSessions(); + history.removeWhere((s) => s.id == id); + + return await prefs.setString( + _kFeedbackHistory, + jsonEncode(history.map((s) => s.toMap()).toList()), + ); + } + + /// 清空所有历史记录 + Future clearAllSessions() async { + final prefs = await _prefs; + return await prefs.remove(_kFeedbackHistory); + } + + /// ═══════════════════════════════════════════════════════════════ + /// 限制检查 + /// ═══════════════════════════════════════════════════════════════ + + /// 检查是否可以提交新反馈 + Future canSubmitFeedback() async { + final prefs = await _prefs; + final now = DateTime.now(); + + // 检查日期重置(新的一天) + await _checkAndResetDailyCount(prefs, now); + + // 获取最后提交时间 + final lastTimeStr = prefs.getString(_kLastFeedbackTime); + if (lastTimeStr != null && lastTimeStr.isNotEmpty) { + final lastTime = DateTime.parse(lastTimeStr); + final minutesSinceLast = now.difference(lastTime).inMinutes; + + if (minutesSinceLast < cooldownMinutes) { + return FeedbackLimitResult( + allowed: false, + reason: LimitReason.cooldown, + remainingMinutes: cooldownMinutes - minutesSinceLast, + ); + } + } + + // 检查今日次数 + final todayCount = prefs.getInt(_kFeedbackCountToday) ?? 0; + if (todayCount >= maxPerDay) { + return FeedbackLimitResult( + allowed: false, + reason: LimitReason.dailyLimit, + remainingCount: 0, + ); + } + + return FeedbackLimitResult( + allowed: true, + reason: null, + remainingCount: maxPerDay - todayCount, + ); + } + + /// 记录一次提交(在发送成功后调用) + Future recordSubmission() async { + final prefs = await _prefs; + final now = DateTime.now(); + + // 更新最后提交时间 + await prefs.setString(_kLastFeedbackTime, now.toIso8601String()); + + // 增加今日计数 + final todayCount = prefs.getInt(_kFeedbackCountToday) ?? 0; + await prefs.setInt(_kFeedbackCountToday, todayCount + 1); + } + + /// 重置今日计数(用于测试或手动重置) + Future resetDailyCount() async { + final prefs = await _prefs; + await prefs.setInt(_kFeedbackCountToday, 0); + await prefs.setString(_kLastResetDate, DateTime.now().toIso8601String()); + } + + /// 获取今日剩余可提交次数 + Future getRemainingSubmissions() async { + final prefs = await _prefs; + await _checkAndResetDailyCount(prefs, DateTime.now()); + final todayCount = prefs.getInt(_kFeedbackCountToday) ?? 0; + return maxPerDay - todayCount; + } + + /// ═══════════════════════════════════════════════════════════════ + /// 私有方法 + /// ═══════════════════════════════════════════════════════════════ + + /// 检查并重置每日计数(如果跨天了) + Future _checkAndResetDailyCount( + SharedPreferences prefs, + DateTime now, + ) async { + final lastResetDateStr = prefs.getString(_kLastResetDate); + + if (lastResetDateStr == null || lastResetDateStr.isEmpty) { + // 首次使用,初始化 + await prefs.setString(_kLastResetDate, now.toIso8601String()); + await prefs.setInt(_kFeedbackCountToday, 0); + return; + } + + final lastResetDate = DateTime.parse(lastResetDateStr); + + // 如果不是同一天,重置计数 + if (now.year != lastResetDate.year || + now.month != lastResetDate.month || + now.day != lastResetDate.day) { + await prefs.setString(_kLastResetDate, now.toIso8601String()); + await prefs.setInt(_kFeedbackCountToday, 0); + } + } +} + +/// 限制原因枚举 +enum LimitReason { + cooldown, // 冷却期内 + dailyLimit, // 达到每日上限 +} + +/// 限制检查结果 +class FeedbackLimitResult { + final bool allowed; // 是否允许提交 + final LimitReason? reason; // 不允许的原因 + final int? remainingMinutes; // 冷却剩余分钟数 + final int? remainingCount; // 今日剩余次数 + + const FeedbackLimitResult({ + required this.allowed, + this.reason, + this.remainingMinutes, + this.remainingCount, + }); + + @override + String toString() { + if (allowed) { + return '✅ 可以提交(今日剩余${remainingCount ?? 0}次)'; + } + switch (reason) { + case LimitReason.cooldown: + return '⏳ 冷却中(还需${remainingMinutes ?? 0}分钟)'; + case LimitReason.dailyLimit: + return '🚫 今日已达上限'; + default: + return '❌ 未知限制'; + } + } +} diff --git a/lib/src/services/system/crash_guard_service.dart b/lib/src/services/system/crash_guard_service.dart index 71d5b7f..5e3411a 100644 --- a/lib/src/services/system/crash_guard_service.dart +++ b/lib/src/services/system/crash_guard_service.dart @@ -164,7 +164,10 @@ class CupertinoDialogReportMode extends ReportMode { @override void requestAction(Report report, BuildContext? context) { - if (kReleaseMode && _isLayoutOverflow(report)) { + if (_isLayoutOverflow(report)) { + debugPrint( + '⚠️ [CrashGuard] LayoutWarning (skipped dialog): ${report.error}', + ); onActionRejected(report); return; } @@ -172,6 +175,7 @@ class CupertinoDialogReportMode extends ReportMode { _tryShowDialog(report, context); } + /// 判断是否为UI布局溢出类错误(非致命,不弹窗但记录日志) bool _isLayoutOverflow(Report report) { final msg = report.error.toString().toLowerCase(); return msg.contains('overflowed') || diff --git a/lib/src/utils/platform_web_stub.dart b/lib/src/utils/platform_web_stub.dart index 97b1176..283f055 100644 --- a/lib/src/utils/platform_web_stub.dart +++ b/lib/src/utils/platform_web_stub.dart @@ -16,6 +16,7 @@ class Platform { static String get executable => ''; static String get resolvedExecutable => ''; static Uri get script => Uri(); + static Uri? get packageConfig => null; static int get numberOfProcessors => 1; static String get pathSeparator => '/'; } diff --git a/lib/src/widgets/common/feature_carousel_card.dart b/lib/src/widgets/common/feature_carousel_card.dart index 0680b57..0702700 100644 --- a/lib/src/widgets/common/feature_carousel_card.dart +++ b/lib/src/widgets/common/feature_carousel_card.dart @@ -361,8 +361,8 @@ class _CarouselPageView extends StatelessWidget { case 1: return _FeatureCarouselItem( icon: '🌟', - title: '应用推荐', - subtitle: '发现更多优质应用', + title: '情景诗词', + subtitle: '工作室旗下的诗词APP', badge: 'NEW', gradient: const [Color(0xFF9C27B0), Color(0xFFE040FB)], onTap: showAppRecommendDialog, diff --git a/lib/src/widgets/navigation_widgets.dart b/lib/src/widgets/navigation_widgets.dart index 55cf65e..cfaa218 100644 --- a/lib/src/widgets/navigation_widgets.dart +++ b/lib/src/widgets/navigation_widgets.dart @@ -101,10 +101,13 @@ class _MainTabViewState extends State { child: IndexedStack( index: currentIndex, children: List.generate(4, (i) { - if (_builtPages.containsKey(i)) { - return _builtPages[i]!; + final page = _builtPages.containsKey(i) + ? _builtPages[i]! + : const SizedBox.shrink(); + if (i != currentIndex) { + return ExcludeFocusTraversal(child: page); } - return const SizedBox.shrink(); + return page; }), ), ), diff --git a/lib/src/widgets/recipe_detail/header/recipe_author_card.dart b/lib/src/widgets/recipe_detail/header/recipe_author_card.dart index 51f4ed3..4f743a7 100644 --- a/lib/src/widgets/recipe_detail/header/recipe_author_card.dart +++ b/lib/src/widgets/recipe_detail/header/recipe_author_card.dart @@ -34,9 +34,7 @@ class RecipeAuthorCard extends StatelessWidget { width: 36, height: 36, decoration: BoxDecoration( - color: - (DesignTokens.dynamicPrimary) - .withValues(alpha: 0.15), + color: (DesignTokens.dynamicPrimary).withValues(alpha: 0.15), shape: BoxShape.circle, ), child: Center( @@ -55,15 +53,30 @@ class RecipeAuthorCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - author!.name, - style: TextStyle( - fontSize: DesignTokens.fontSm, - fontWeight: FontWeight.w600, - color: isDark - ? DarkDesignTokens.text1 - : DesignTokens.text1, - ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + author!.name, + style: TextStyle( + fontSize: DesignTokens.fontSm, + fontWeight: FontWeight.w600, + color: isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1, + ), + ), + ), + Text( + '贡献者', + style: TextStyle( + fontSize: DesignTokens.fontXs, + fontWeight: FontWeight.w500, + color: DesignTokens.dynamicPrimary, + ), + ), + ], ), if (author!.alias != null && author!.alias!.isNotEmpty) Text( diff --git a/lib/src/widgets/recipe_detail/interaction/recipe_email_button.dart b/lib/src/widgets/recipe_detail/interaction/recipe_email_button.dart index 80f5897..c378894 100644 --- a/lib/src/widgets/recipe_detail/interaction/recipe_email_button.dart +++ b/lib/src/widgets/recipe_detail/interaction/recipe_email_button.dart @@ -824,6 +824,27 @@ class _EmailDialogState extends State<_EmailDialog> { _buildPreviewCard(isDark), const SizedBox(height: DesignTokens.space3), + // 📨 收件人邮箱(移至线路选择上方) + _buildSectionLabel('📨 收件人邮箱', isDark), + const SizedBox(height: DesignTokens.space1), + _buildTextField( + controller: _recipientController, + placeholder: '请输入收件人邮箱地址', + keyboardType: TextInputType.emailAddress, + isDark: isDark, + ), + const SizedBox(height: DesignTokens.space2), + Text( + '💡 菜谱详情(食材、步骤、营养信息)将以精美HTML邮件发送', + style: TextStyle( + fontSize: DesignTokens.fontXs, + color: isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3, + ), + ), + const SizedBox(height: DesignTokens.space3), + // 🔀 发送线路选择 _buildSectionLabel('🔀 发送线路', isDark), const SizedBox(height: DesignTokens.space2), @@ -884,26 +905,6 @@ class _EmailDialogState extends State<_EmailDialog> { const SizedBox(height: DesignTokens.space3), ], - // 📨 收件人邮箱 - _buildSectionLabel('📨 收件人邮箱', isDark), - const SizedBox(height: DesignTokens.space1), - _buildTextField( - controller: _recipientController, - placeholder: '请输入收件人邮箱地址', - keyboardType: TextInputType.emailAddress, - isDark: isDark, - ), - const SizedBox(height: DesignTokens.space2), - Text( - '💡 菜谱详情(食材、步骤、营养信息)将以精美HTML邮件发送', - style: TextStyle( - fontSize: DesignTokens.fontXs, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, - ), - ), - const SizedBox(height: DesignTokens.space2), _buildDailyLimitHint(isDark), const SizedBox(height: DesignTokens.space4), ], @@ -923,101 +924,155 @@ class _EmailDialogState extends State<_EmailDialog> { final routeInfo = route == EmailRouteType.custom ? null : EmailService.presetRoutes[route.index]; - final label = route == EmailRouteType.custom - ? '🔧 自定义SMTP' - : '${routeInfo!.icon} ${routeInfo.name}'; + final bool isVipRoute = route == EmailRouteType.official2; + + String label; + String? subtitle; + + if (route == EmailRouteType.custom) { + label = '🔧 自定义SMTP'; + subtitle = '使用自己的邮箱服务器发送'; + } else if (isVipRoute) { + label = '✨ VIP 专属邮箱'; + subtitle = '高级会员专属线路,更稳定快速'; + } else { + label = '${routeInfo!.icon} ${routeInfo.name}'; + final maskedHost = _maskUrl(routeInfo.host); + subtitle = '${routeInfo.username} · $maskedHost'; + } return Padding( padding: const EdgeInsets.only(bottom: DesignTokens.space2), child: GestureDetector( - onTap: () => setState(() => _selectedRoute = route), + onTap: isVipRoute + ? null + : () => setState(() => _selectedRoute = route), child: Container( padding: const EdgeInsets.symmetric( horizontal: DesignTokens.space3, vertical: DesignTokens.space3, ), decoration: BoxDecoration( - color: isSelected - ? DesignTokens.dynamicPrimaryLight - : (isDark - ? DarkDesignTokens.background - : DesignTokens.background), + color: isVipRoute + ? (isDark ? const Color(0xFF2D1B4E) : const Color(0xFFF8F0FF)) + : (isSelected + ? DesignTokens.dynamicPrimaryLight + : (isDark + ? DarkDesignTokens.background + : DesignTokens.background)), borderRadius: DesignTokens.borderRadiusMd, border: Border.all( - color: isSelected - ? DesignTokens.dynamicPrimary - : DesignTokens.text3.withValues(alpha: 0.15), - width: isSelected ? 1.5 : 0.5, + color: isVipRoute + ? (const Color(0xFF9B59B6)).withValues(alpha: 0.5) + : (isSelected + ? DesignTokens.dynamicPrimary + : DesignTokens.text3.withValues(alpha: 0.15)), + width: isSelected ? 1.5 : (isVipRoute ? 1.2 : 0.5), ), ), child: Row( children: [ - // 选中指示器 + // 选中指示器 / VIP标识 Container( width: 20, height: 20, decoration: BoxDecoration( shape: BoxShape.circle, - color: isSelected - ? DesignTokens.dynamicPrimary - : CupertinoColors.transparent, + color: isVipRoute + ? (const Color(0xFF9B59B6)) + : (isSelected + ? DesignTokens.dynamicPrimary + : CupertinoColors.transparent), border: Border.all( - color: isSelected - ? DesignTokens.dynamicPrimary - : (isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3), + color: isVipRoute + ? (const Color(0xFF9B59B6)) + : (isSelected + ? DesignTokens.dynamicPrimary + : (isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3)), width: 1.5, ), ), - child: isSelected - ? const Icon( - CupertinoIcons.checkmark, - size: 12, - color: CupertinoColors.white, + child: isVipRoute + ? const Center( + child: Text('👑', style: TextStyle(fontSize: 12)), ) - : null, + : (isSelected + ? const Icon( + CupertinoIcons.checkmark, + size: 12, + color: CupertinoColors.white, + ) + : null), ), const SizedBox(width: DesignTokens.space3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - label, - style: TextStyle( - fontSize: DesignTokens.fontMd, - fontWeight: FontWeight.w600, - color: isSelected - ? DesignTokens.dynamicPrimary - : (isDark - ? DarkDesignTokens.text1 - : DesignTokens.text1), - ), - ), - if (routeInfo != null) - Text( - '${routeInfo.username} · ${routeInfo.host}', - style: TextStyle( - fontSize: DesignTokens.fontXs, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, + Row( + children: [ + Text( + label, + style: TextStyle( + fontSize: DesignTokens.fontMd, + fontWeight: FontWeight.w600, + color: isVipRoute + ? (const Color(0xFF9B59B6)) + : (isSelected + ? DesignTokens.dynamicPrimary + : (isDark + ? DarkDesignTokens.text1 + : DesignTokens.text1)), + ), ), - ), - if (route == EmailRouteType.custom) + if (isVipRoute) ...[ + const SizedBox(width: DesignTokens.space2), + Container( + padding: const EdgeInsets.symmetric( + horizontal: DesignTokens.space2, + vertical: 1, + ), + decoration: BoxDecoration( + color: const Color(0xFF9B59B6), + borderRadius: DesignTokens.borderRadiusFull, + ), + child: const Text( + 'VIP', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w700, + color: CupertinoColors.white, + ), + ), + ), + ], + ], + ), + if (subtitle != null) Text( - '使用自己的邮箱服务器发送', + subtitle, style: TextStyle( fontSize: DesignTokens.fontXs, - color: isDark - ? DarkDesignTokens.text3 - : DesignTokens.text3, + color: isVipRoute + ? (const Color( + 0xFF9B59B6, + )).withValues(alpha: 0.7) + : (isDark + ? DarkDesignTokens.text3 + : DesignTokens.text3), ), ), ], ), ), + if (isVipRoute) + Icon( + CupertinoIcons.lock_fill, + size: 16, + color: const Color(0xFF9B59B6).withValues(alpha: 0.6), + ), ], ), ), @@ -1026,6 +1081,14 @@ class _EmailDialogState extends State<_EmailDialog> { }).toList(); } + /// URL脱敏处理:中间部分用***代替 + String _maskUrl(String url) { + if (url.length <= 8) return url; + final start = url.substring(0, 4); + final end = url.substring(url.length - 4); + return '$start***$end'; + } + /// 菜谱信息预览卡片 Widget _buildPreviewCard(bool isDark) { return Container( diff --git a/ohos/AppScope/app.json5 b/ohos/AppScope/app.json5 index 4595231..3080fe7 100644 --- a/ohos/AppScope/app.json5 +++ b/ohos/AppScope/app.json5 @@ -1,9 +1,9 @@ { "app": { "bundleName": "cute.major.kitchen", - "vendor": "example", - "versionCode": 1000000, - "versionName": "1.0.0", + "vendor": "微风暴", + "versionCode": 26042001, + "versionName": "1.1.0", "icon": "$media:app_icon", "label": "$string:app_name" } diff --git a/ohos/AppScope/resources/base/element/string.json b/ohos/AppScope/resources/base/element/string.json index 746607a..698fae3 100644 --- a/ohos/AppScope/resources/base/element/string.json +++ b/ohos/AppScope/resources/base/element/string.json @@ -2,7 +2,7 @@ "string": [ { "name": "app_name", - "value": "ohos" + "value": "小妈厨房" } ] } diff --git a/ohos/build-profile.json5 b/ohos/build-profile.json5 index 63deb27..7bce618 100644 --- a/ohos/build-profile.json5 +++ b/ohos/build-profile.json5 @@ -34,7 +34,7 @@ "signingConfig": "default", "compatibleSdkVersion": "5.1.0(18)", "runtimeOS": "HarmonyOS", - "targetSdkVersion": "6.0.2(22)" + "targetSdkVersion": "6.1.0(23)" }, { "name": "release", diff --git a/ohos/entry/src/main/resources/base/element/string.json b/ohos/entry/src/main/resources/base/element/string.json index b550114..812418f 100644 --- a/ohos/entry/src/main/resources/base/element/string.json +++ b/ohos/entry/src/main/resources/base/element/string.json @@ -2,31 +2,31 @@ "string": [ { "name": "module_desc", - "value": "module description" + "value": "Cute Kitchen Application Module" }, { "name": "EntryAbility_desc", - "value": "description" + "value": "Cute Kitchen Main Entry Point" }, { "name": "EntryAbility_label", - "value": "ohos" + "value": "Cute Kitchen" }, { "name": "permission_internet_reason", - "value": "Network access required for app functionality" + "value": "Network access is required for recipe browsing and data synchronization" }, { "name": "permission_vibrate_reason", - "value": "Vibrate feedback for user interactions" + "value": "Vibration feedback is used for cooking timers and notifications" }, { "name": "permission_clipboard_reason", - "value": "Access to clipboard for paste operations" + "value": "Clipboard access is used for sharing recipes and ingredients" }, { "name": "permission_read_media_reason", - "value": "Read media files for data import" + "value": "Media access is required for importing recipe images and data" } ] } \ No newline at end of file diff --git a/ohos/entry/src/main/resources/en_US/element/string.json b/ohos/entry/src/main/resources/en_US/element/string.json index 0bc2364..19499c8 100644 --- a/ohos/entry/src/main/resources/en_US/element/string.json +++ b/ohos/entry/src/main/resources/en_US/element/string.json @@ -2,15 +2,15 @@ "string": [ { "name": "module_desc", - "value": "module description" + "value": "Cute Kitchen Module" }, { "name": "EntryAbility_desc", - "value": "description" + "value": "Cute Kitchen Main Entry" }, { "name": "EntryAbility_label", - "value": "ohos" + "value": "Cute Kitchen" }, { "name": "permission_read_media_reason", diff --git a/ohos/entry/src/main/resources/zh_CN/element/string.json b/ohos/entry/src/main/resources/zh_CN/element/string.json index 62485cc..21e690f 100644 --- a/ohos/entry/src/main/resources/zh_CN/element/string.json +++ b/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -2,15 +2,15 @@ "string": [ { "name": "module_desc", - "value": "模块描述" + "value": "小妈厨房模块" }, { "name": "EntryAbility_desc", - "value": "description" + "value": "小妈厨房主入口" }, { "name": "EntryAbility_label", - "value": "ohos" + "value": "小妈厨房" }, { "name": "permission_read_media_reason", diff --git a/packages/file_picker b/packages/file_picker new file mode 160000 index 0000000..433c8f6 --- /dev/null +++ b/packages/file_picker @@ -0,0 +1 @@ +Subproject commit 433c8f6567ae6224e1446d001045499014a24461 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 78266a5..5e23976 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 @@ -5,11 +5,11 @@ "versionCode": 100, "versionName": "0.99.1", "minAPIVersion": 50100018, - "targetAPIVersion": 60002022, + "targetAPIVersion": 60100023, "apiReleaseType": "Release", "targetMinorAPIVersion": 0, "targetPatchAPIVersion": 0, - "compileSdkVersion": "6.0.2.130", + "compileSdkVersion": "6.1.0.105", "compileSdkType": "HarmonyOS", "appEnvironments": [], "bundleType": "app", diff --git a/packages/fluttertoast_ohos/ohos/build/release/intermediates/merge_profile/default/module.json b/packages/fluttertoast_ohos/ohos/build/release/intermediates/merge_profile/default/module.json deleted file mode 100644 index da5d364..0000000 --- a/packages/fluttertoast_ohos/ohos/build/release/intermediates/merge_profile/default/module.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "app": { - "bundleName": "cute.major.kitchen", - "debug": false, - "versionCode": 95, - "versionName": "0.96.0", - "minAPIVersion": 50100018, - "targetAPIVersion": 60002022, - "apiReleaseType": "Release", - "targetMinorAPIVersion": 0, - "targetPatchAPIVersion": 0, - "compileSdkVersion": "6.0.2.130", - "compileSdkType": "HarmonyOS", - "appEnvironments": [], - "bundleType": "app", - "buildMode": "release" - }, - "module": { - "name": "fluttertoast_ohos", - "type": "har", - "deviceTypes": [ - "default", - "tablet" - ], - "packageName": "fluttertoast_ohos", - "installationFree": false - } -} 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 568cf85..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-e34a685f4b'; -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 5332e9c..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-e34a685f4b","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 b962bde..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/systemchannels/NativeVsyncChannel.ets +++ /dev/null @@ -1,91 +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 "checkLTPOSwitchStatus": - // Check LTPO switch state - result.success(FlutterNapi.checkLTPOSwitchState()); - break; - default: - result.notImplemented(); - 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 2b799f7..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(); - this.delegate?.changeColorMode(config.colorMode); - } - - /** - * 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 30bd7fc..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterAbilityAndEntryDelegate.ets +++ /dev/null @@ -1,705 +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'; -import { ConfigurationConstant } from '@kit.AbilityKit'; -import { resourceManager } from '@kit.LocalizationKit'; -import { EmbeddingNodeController } from './EmbeddingNodeController'; - -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; - private currentColorMode?: ConfigurationConstant.ColorMode; - - /** - * 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(); - const config = this.context.resourceManager.getConfigurationSync(); - this.currentColorMode = - config.colorMode === resourceManager.ColorMode.DARK ? ConfigurationConstant.ColorMode.COLOR_MODE_DARK : - ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT; - } - 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!!); - } - this.platformPlugin?.setFlutterView(this.flutterView); - 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() - } - } - - changeColorMode(colorMode?: ConfigurationConstant.ColorMode) { - if (colorMode !== undefined && this.currentColorMode !== colorMode) { - this.currentColorMode = colorMode; - let length = this.flutterView?.getDVModel()?.children.length ?? 0; - for (let index = length - 1; index >= 0; index--) { - const dvModel = this.flutterView?.getDVModel()?.children[index]; - const params = dvModel?.getLayoutParams() as Record; - const nodeController = params['nodeController'] as EmbeddingNodeController; - if (nodeController && nodeController.rebuild) { - nodeController.rebuild(); - } - } - } - } -} - -/** - * 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 6b449a3..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(); - this.delegate?.changeColorMode(config.colorMode); - }, - 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 81a2780..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterManager.ets +++ /dev/null @@ -1,575 +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; - } - - /** - * Sets the visibility of a specific system bar and applies safe area padding. - * When hiding a system bar, automatically applies padding to avoid content being covered. - * @param flutterViewId - The ID of the FlutterView to apply padding - * @param specificSystemBar - System bar type: 'status' for status bar, 'navigation' for three-key navigation bar, 'navigationIndicator' for gesture navigation bar - * @param isVisible - True to show, false to hide - * @param enableAnimation - Whether to enable animation effect, optional - * @param context - Optional context, uses the first UIAbility if not provided - */ - setSpecificSystemBarEnabled(flutterViewId: string, specificSystemBar: 'status' | 'navigation' | 'navigationIndicator', isVisible: boolean, enableAnimation?: boolean, context?: Context): void { - const flutterView: FlutterView | null = this.getFlutterView(flutterViewId); - if (!flutterView) { - Log.e(TAG, `setSpecificSystemBarEnabled failed: flutterView not found for id ${flutterViewId}`); - return; - } - - try { - const windowStage = this.getWindowStage(this.getUIAbility(context)); - if (!windowStage) { - Log.e(TAG, 'setSpecificSystemBarEnabled failed: windowStage is null'); - return; - } - - const mainWindow = windowStage.getMainWindowSync(); - if (!mainWindow) { - Log.e(TAG, 'setSpecificSystemBarEnabled failed: mainWindow is null'); - return; - } - mainWindow.setSpecificSystemBarEnabled(specificSystemBar, isVisible, enableAnimation); - - switch (specificSystemBar) { - case 'status': - // Apply top padding when hiding status bar to avoid content being covered - const statusBarHeight = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height; - if (statusBarHeight > 0) { - flutterView.setPaddingTop(statusBarHeight); - } - break; - case 'navigation': - case 'navigationIndicator': - // Apply bottom padding when hiding navigation bar to avoid content being covered - const navHeight = mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR).bottomRect.height; - if (navHeight > 0) { - flutterView.setPaddingBottom(navHeight); - } - break; - } - - Log.i(TAG, `setSpecificSystemBarEnabled: ${specificSystemBar} = ${isVisible}, animation = ${enableAnimation} success`); - } catch (error) { - Log.e(TAG, `setSpecificSystemBarEnabled error: ${JSON.stringify(error)}`); - } - } - - /** - * Adjusts FlutterView top padding to avoid window decoration overlap. - * @param flutterViewId - The FlutterView ID - * @param context - Optional context, defaults to first UIAbility - */ - handleWindowDecorSafeArea(flutterViewId: string, context?: Context | null | undefined): void { - if (!this.isWindowDecorSafeAreaAvoidSupported(context)) { - return; - } - const flutterView = this.getFlutterView(flutterViewId); - if (!flutterView) { - return; - } - const mainWindow = this.getWindowStage(this.getUIAbility(context ?? undefined))?.getMainWindowSync(); - if (!mainWindow) { - return; - } - // Clear padding if window decoration is visible - if (mainWindow.getWindowDecorVisible() ) { - flutterView.setPaddingTop(0); - return; - } - // Clear padding if title buttons are hidden - const titleButtonRect = mainWindow.getTitleButtonRect(); - if (!titleButtonRect || titleButtonRect.height <= 0) { - flutterView.setPaddingTop(0); - return; - } - // Set padding to match title button height - flutterView.setPaddingTop(vp2px(titleButtonRect.height)); - } - - /** - * Checks if window decoration safe area avoidance is supported on the current device. - * Returns true only when the device is in freeform multi-window mode and supports window decoration. - * @param context - Optional context, defaults to first UIAbility - * @returns True if window decoration safe area avoidance is supported, false otherwise - */ - isWindowDecorSafeAreaAvoidSupported(context?: Context | null | undefined): boolean { - // API 18+ required for window decoration APIs - if (deviceInfo.sdkApiVersion < 18) { - return false; - } - const mainWindow = this.getWindowStage(this.getUIAbility(context ?? undefined))?.getMainWindowSync(); - if (!mainWindow) { - return false; - } - try { - // Avoid compilation errors: getWindowDecorVisible is an API18 interface - // Check if getWindowDecorVisible API is available - const result = typeof (mainWindow as ESObject)?.getWindowDecorVisible(); - return result == 'boolean'; - } catch (exception) { - Log.i(TAG, `Failed to check window decor visibility support. Cause code: ${exception.code}, message: ${exception.message}`); - return false; - } - } -} - -/** - * 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 ed4c4f7..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/ohos/FlutterPage.ets +++ /dev/null @@ -1,354 +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) - .onAreaChange((oldValue: Area, newValue: Area) => { - // Only handle when Y position changes (window decoration state changed) - if (oldValue.globalPosition.y != newValue.globalPosition.y) { - FlutterManager.getInstance().handleWindowDecorSafeArea(this.viewId, this.getUIContext().getHostContext()); - } - }) - 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) - .onAreaChange((oldValue: Area, newValue: Area) => { - // Only handle when Y position changes (window decoration state changed) - if (oldValue.globalPosition.y != newValue.globalPosition.y) { - FlutterManager.getInstance().handleWindowDecorSafeArea(this.viewId, this.getUIContext().getHostContext()); - } - }) - - 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 39904c8..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/PlatformPlugin.ets +++ /dev/null @@ -1,551 +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'; -import { FlutterView } from '../view/FlutterView'; - -/** - * 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 (FlutterManager.getInstance().isWindowDecorSafeAreaAvoidSupported(this.callback.context)) { - const shouldHideWindowDecor = this.callback.shouldHideWindowDecor(); - this.callback.mainWindow?.setWindowDecorVisible(!shouldHideWindowDecor); - - // Adjust padding based on window decoration visibility - if (shouldHideWindowDecor) { - const titleButtonRect = this.callback.mainWindow?.getTitleButtonRect(); - const paddingHeight = (titleButtonRect && titleButtonRect.height > 0) ? titleButtonRect.height : 0; - this.callback.flutterView?.setPaddingTop(vp2px(paddingHeight)); - } else { - this.callback.flutterView?.setPaddingTop(0); - } - } - - 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) { - } - }) - } - } - - /** - * Binds a FlutterView to this plugin. - * @param flutterView - The FlutterView instance - */ - setFlutterView(flutterView: FlutterView): void { - this.callback.flutterView = flutterView; - } - - /** - * 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; - /** The FlutterView instance for padding control. */ - flutterView: FlutterView | 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(); - } - - /** - * Determines whether to hide window decoration based on system UI overlay settings. - * @returns True to hide window decoration, false to show it - */ - shouldHideWindowDecor(): boolean { - const hasStatus = this.showBarOrNavigation.includes('status'); - const hasNavigation = this.showBarOrNavigation.includes('navigation'); - - // Fullscreen: [] → hide - // Edge-to-edge: ['status', 'navigation'] → hide - // Bottom only: ['navigation'] → hide - // Top only: ['status'] → show - - return !hasStatus || hasNavigation; - } -} 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 21689d3..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/plugin/mouse/MouseCursorPlugin.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 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.e(TAG, "setPointerStyle failed: windowId is invalid: " + this.windowId); - return; - } - let pointStyle: pointer.PointerStyle = this.resolveSystemCursor(kind); - try { - pointer.setPointerStyle(this.windowId, pointStyle, (err: Any) => { - if (err) { - Log.e(TAG, "setPointerStyle callback error: kind=" + kind + ", err=" + JSON.stringify(err)); - } else { - Log.i(TAG, "setPointerStyle success: kind=" + kind); - } - }) - } catch (e) { - Log.e(TAG, "setPointerStyle exception: kind=" + kind + ", error=" + 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 09fad2b..0000000 --- a/packages/fluttertoast_ohos/ohos/oh_modules/@ohos/flutter_ohos/src/main/ets/view/FlutterView.ets +++ /dev/null @@ -1,1212 +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 paddingBottom?: 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); - - // 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 densityUpdateCallback = (info: uiObserver.DensityInfo) => { - try { - const sysDensity = display.getDefaultDisplaySync().densityPixels; - const customDensity = info.density; - if (sysDensity > 0 && customDensity > 0 && Number.isFinite(customDensity)) { - const scale = customDensity / sysDensity; - Log.i(TAG, "densityUpdateCallback: customDensity=" + customDensity + ", sysDensity=" + - sysDensity + ", scale=" + scale); - this.onDevicePixelRatioChange(scale); - } - } catch (e) { - Log.e(TAG, "densityUpdateCallback error: " + JSON.stringify(e)); - } - } - - 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); - } - private windowTitleButtonRectChangeCallback = (data: window.TitleButtonRect) => { - Log.i(TAG, "windowTitleButtonRectChangeCallback " + JSON.stringify(data)); - FlutterManager.getInstance().handleWindowDecorSafeArea(this.id, this.context); - } - - /** - * 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.uiContext as UIContext, this.routerPageUpdateCallback); - uiObserver.off('densityUpdate', this.uiContext as UIContext, this.densityUpdateCallback); - 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); - this.mainWindow?.off('windowTitleButtonRectChange', this.windowTitleButtonRectChangeCallback); - 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; - // Listen to route navigation (Flutter navigating to HarmonyOS native), soft keyboard management - uiObserver.on('routerPageUpdate', this.uiContext as UIContext, this.routerPageUpdateCallback); - uiObserver.on('densityUpdate', this.uiContext as UIContext, this.densityUpdateCallback); - this.sendSettings(); - this.mainWindow?.on('windowTitleButtonRectChange', this.windowTitleButtonRectChangeCallback); - 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; - } - if (this.paddingBottom != undefined) { - this.viewportMetrics.physicalViewPaddingBottom = this.paddingBottom; - } else { - this.viewportMetrics.physicalViewPaddingBottom = - this.systemAvoidArea?.bottomRect.height ?? this.viewportMetrics.physicalViewPaddingBottom; - } - } else { // 非全屏显示 - this.viewportMetrics.physicalViewPaddingTop = this.paddingTop ?? 0; - this.viewportMetrics.physicalViewPaddingBottom = this.paddingBottom ?? 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); - } - - /** - * Sets the bottom padding value. - * @param paddingBottom - The bottom padding value, or undefined to use default - */ - setPaddingBottom(paddingBottom?: number) { - this.paddingBottom = paddingBottom; - 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 568cf85..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-e34a685f4b'; -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/Index.ets b/packages/fluttertoast_ohos/ohos/oh_modules/flutter_native_arm64_v8a/Index.ets deleted file mode 100644 index e69de29..0000000 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 d89413c..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 a97ed01..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-e34a685f4b","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/packages/mailer b/packages/mailer new file mode 160000 index 0000000..8c717c4 --- /dev/null +++ b/packages/mailer @@ -0,0 +1 @@ +Subproject commit 8c717c453b460fd342ebfe155264d8cf09302b5f diff --git a/packages/qr b/packages/qr new file mode 160000 index 0000000..7922887 --- /dev/null +++ b/packages/qr @@ -0,0 +1 @@ +Subproject commit 792288784e200301b323b1e36852ae60a3ac2431 diff --git a/pubspec.yaml b/pubspec.yaml index 8a4cdbc..ee8f542 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.99.1+100 +version: 1.3.0+260421 environment: sdk: ^3.9.2 diff --git a/scripts/add_image_border.py b/scripts/add_image_border.py new file mode 100644 index 0000000..0f52ea2 --- /dev/null +++ b/scripts/add_image_border.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +from PIL import Image, ImageDraw, ImageFilter +import os + +def add_border_and_resize(input_path, output_path, target_size=(1920, 1080), border_color=(255, 255, 255), border_width=20): + img = Image.open(input_path) + original_width, original_height = img.size + aspect_ratio = original_width / original_height + target_aspect = target_size[0] / target_size[1] + + if aspect_ratio > target_aspect: + new_width = target_size[0] - border_width * 2 + new_height = int(new_width / aspect_ratio) + else: + new_height = target_size[1] - border_width * 2 + new_width = int(new_height * aspect_ratio) + + img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS) + output_img = Image.new('RGB', target_size, border_color) + paste_x = (target_size[0] - new_width) // 2 + paste_y = (target_size[1] - new_height) // 2 + + shadow_offset = 8 + shadow_rect = [ + paste_x + shadow_offset, + paste_y + shadow_offset, + paste_x + new_width + shadow_offset, + paste_y + new_height + shadow_offset + ] + shadow_layer = Image.new('RGBA', target_size, (0, 0, 0, 0)) + shadow_draw = ImageDraw.Draw(shadow_layer) + shadow_draw.rectangle(shadow_rect, fill=(0, 0, 0, 60)) + shadow_blurred = shadow_layer.filter(ImageFilter.GaussianBlur(radius=10)) + + final_image = Image.new('RGB', target_size, border_color) + + if shadow_blurred.mode == 'RGBA': + final_image.paste(shadow_blurred, (0, 0), shadow_blurred) + else: + final_rgb_shadow = shadow_blurred.convert('RGB') + final_image.paste(final_rgb_shadow, (0, 0)) + + final_image.paste(img_resized, (paste_x, paste_y)) + + draw = ImageDraw.Draw(final_image) + rect = [paste_x - 2, paste_y - 2, paste_x + new_width + 2, paste_y + new_height + 2] + for i in range(3): + offset = i + 1 + current_rect = [ + rect[0] - offset, + rect[1] - offset, + rect[2] + offset, + rect[3] + offset + ] + draw.rectangle(current_rect, outline=(180, 200, 220)) + + for i in range(border_width): + current_rect = [ + rect[0] - i - 3, + rect[1] - i - 3, + rect[2] + i + 3, + rect[3] + i + 3 + ] + alpha = int(255 * (1 - i / border_width)) + color_with_alpha = (200 + i*2, 210 + i*2, 230 + i*2) + draw.rectangle(current_rect, outline=color_with_alpha) + + final_image.save(output_path, quality=95) + print(f'[OK] Processed: {os.path.basename(input_path)} -> {output_path}') + return True + +def main(): + input_files = [ + r'e:\project\flutter\f\mom_kitchen\docs\design\1.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\2.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\3.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\4.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\5.jpg', + ] + + output_dir = r'e:\project\flutter\f\mom_kitchen\docs\design\processed' + os.makedirs(output_dir, exist_ok=True) + + print('='*50) + print('Image Border Processing Script') + print(f'Target Size: 1920 x 1080 px') + print(f'Processing: {len(input_files)} images') + print('='*50) + print() + + success_count = 0 + for input_file in input_files: + if os.path.exists(input_file): + filename = os.path.basename(input_file) + output_path = os.path.join(output_dir, f'bordered_{filename}') + try: + add_border_and_resize(input_file, output_path) + success_count += 1 + except Exception as e: + print(f'[ERROR] Failed to process: {filename} - {str(e)}') + import traceback + traceback.print_exc() + else: + print(f'[WARNING] File not found: {input_file}') + + print() + print('='*50) + print(f'[DONE] Processed {success_count}/{len(input_files)} images successfully!') + print(f'Output directory: {output_dir}') + print('='*50) + +if __name__ == '__main__': + main() diff --git a/scripts/add_image_border_portrait.py b/scripts/add_image_border_portrait.py new file mode 100644 index 0000000..c2ff66a --- /dev/null +++ b/scripts/add_image_border_portrait.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +from PIL import Image, ImageDraw, ImageFilter +import os + +def add_border_and_resize(input_path, output_path, target_size=(1080, 1920), border_color=(255, 255, 255), border_width=20): + img = Image.open(input_path) + original_width, original_height = img.size + aspect_ratio = original_width / original_height + target_aspect = target_size[0] / target_size[1] + + if aspect_ratio > target_aspect: + new_width = target_size[0] - border_width * 2 + new_height = int(new_width / aspect_ratio) + else: + new_height = target_size[1] - border_width * 2 + new_width = int(new_height * aspect_ratio) + + img_resized = img.resize((new_width, new_height), Image.Resampling.LANCZOS) + paste_x = (target_size[0] - new_width) // 2 + paste_y = (target_size[1] - new_height) // 2 + + shadow_offset = 8 + shadow_rect = [ + paste_x + shadow_offset, + paste_y + shadow_offset, + paste_x + new_width + shadow_offset, + paste_y + new_height + shadow_offset + ] + shadow_layer = Image.new('RGBA', target_size, (0, 0, 0, 0)) + shadow_draw = ImageDraw.Draw(shadow_layer) + shadow_draw.rectangle(shadow_rect, fill=(0, 0, 0, 60)) + shadow_blurred = shadow_layer.filter(ImageFilter.GaussianBlur(radius=10)) + + final_image = Image.new('RGB', target_size, border_color) + + if shadow_blurred.mode == 'RGBA': + final_image.paste(shadow_blurred, (0, 0), shadow_blurred) + else: + final_rgb_shadow = shadow_blurred.convert('RGB') + final_image.paste(final_rgb_shadow, (0, 0)) + + final_image.paste(img_resized, (paste_x, paste_y)) + + draw = ImageDraw.Draw(final_image) + rect = [paste_x - 2, paste_y - 2, paste_x + new_width + 2, paste_y + new_height + 2] + for i in range(3): + offset = i + 1 + current_rect = [ + rect[0] - offset, + rect[1] - offset, + rect[2] + offset, + rect[3] + offset + ] + draw.rectangle(current_rect, outline=(180, 200, 220)) + + for i in range(border_width): + current_rect = [ + rect[0] - i - 3, + rect[1] - i - 3, + rect[2] + i + 3, + rect[3] + i + 3 + ] + color_with_alpha = (200 + i*2, 210 + i*2, 230 + i*2) + draw.rectangle(current_rect, outline=color_with_alpha) + + final_image.save(output_path, quality=95) + print(f'[OK] Processed: {os.path.basename(input_path)} -> {output_path}') + return True + +def main(): + input_files = [ + r'e:\project\flutter\f\mom_kitchen\docs\design\1.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\2.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\3.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\4.jpg', + r'e:\project\flutter\f\mom_kitchen\docs\design\5.jpg', + ] + + output_dir = r'e:\project\flutter\f\mom_kitchen\docs\design\processed\portrait' + os.makedirs(output_dir, exist_ok=True) + + print('='*50) + print('Image Border Processing Script (Portrait Mode)') + print(f'Target Size: 1080 x 1920 px (Vertical)') + print(f'Processing: {len(input_files)} images') + print('='*50) + print() + + success_count = 0 + for input_file in input_files: + if os.path.exists(input_file): + filename = os.path.basename(input_file) + output_path = os.path.join(output_dir, f'bordered_portrait_{filename}') + try: + add_border_and_resize(input_file, output_path) + success_count += 1 + except Exception as e: + print(f'[ERROR] Failed to process: {filename} - {str(e)}') + import traceback + traceback.print_exc() + else: + print(f'[WARNING] File not found: {input_file}') + + print() + print('='*50) + print(f'[DONE] Processed {success_count}/{len(input_files)} images successfully!') + print(f'Output directory: {output_dir}') + print('='*50) + +if __name__ == '__main__': + main() diff --git a/scripts/package_windows.ps1 b/scripts/package_windows.ps1 new file mode 100644 index 0000000..efc9ddd --- /dev/null +++ b/scripts/package_windows.ps1 @@ -0,0 +1,65 @@ +# ============================================================================ +# 小妈厨房 - Windows 打包脚本 +# ============================================================================ +# 使用方法: +# .\scripts\package_windows.ps1 # 完整流程:构建+打包 +# .\scripts\package_windows.ps1 -SkipBuild # 跳过构建,仅打包 +# +# 版本号自动从 pubspec.yaml 读取,无需手动同步 +# ============================================================================ + +param( + [switch]$SkipBuild = $false +) + +$ErrorActionPreference = "Stop" +$ProjectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +Set-Location $ProjectRoot + +# ---- 从 pubspec.yaml 读取版本号 ---- +$pubspec = Get-Content "pubspec.yaml" -Raw +if ($pubspec -match "version:\s*(\d+\.\d+\.\d+)(?:\+(\d+))?") { + $AppVersion = $Matches[1] + $BuildNumber = if ($Matches[2]) { $Matches[2] } else { "0" } + Write-Host "Version: $AppVersion (build $BuildNumber)" -ForegroundColor Green +} else { + Write-Host "ERROR: Cannot parse version from pubspec.yaml" -ForegroundColor Red + exit 1 +} + +# ---- 构建 Flutter Windows 应用 ---- +if (-not $SkipBuild) { + Write-Host "`n--- Building Flutter Windows app ---" -ForegroundColor Yellow + flutter build windows + if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Flutter build failed" -ForegroundColor Red + exit 1 + } + Write-Host "Build successful" -ForegroundColor Green +} else { + Write-Host "`n--- Skipping build (use -SkipBuild flag) ---" -ForegroundColor Yellow +} + +# ---- Inno Setup 打包(通过 /D 传递版本号) ---- +Write-Host "`n--- Packaging with Inno Setup ---" -ForegroundColor Yellow +$isccPath = "d:\Program Files (x86)\Inno Setup 6\ISCC.exe" +if (-not (Test-Path $isccPath)) { + Write-Host "ERROR: ISCC.exe not found at $isccPath" -ForegroundColor Red + exit 1 +} + +& $isccPath "/DAppVer=$AppVersion" "installer.iss" +if ($LASTEXITCODE -ne 0) { + Write-Host "ERROR: Inno Setup packaging failed" -ForegroundColor Red + exit 1 +} + +Write-Host "`n========================================" -ForegroundColor Green +Write-Host " Package created successfully!" -ForegroundColor Green +Write-Host " Version: $AppVersion" -ForegroundColor Green +$outputFile = "dist\MomKitchen_Setup_$AppVersion.exe" +if (Test-Path $outputFile) { + $size = (Get-Item $outputFile).Length / 1MB + Write-Host " Output: $outputFile ($([math]::Round($size, 1)) MB)" -ForegroundColor Green +} +Write-Host "========================================" -ForegroundColor Green diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 62b3ca3..c9d6910 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -41,6 +41,7 @@ function(APPLY_STANDARD_SETTINGS TARGET) target_compile_features(${TARGET} PUBLIC cxx_std_17) target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_options(${TARGET} PRIVATE /utf-8) target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") endfunction() diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 7b60569..5b80604 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -89,13 +89,13 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "mom_kitchen" "\0" + VALUE "CompanyName", "MomKitchen" "\0" + VALUE "FileDescription", "小妈厨房" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "mom_kitchen" "\0" - VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 MomKitchen. All rights reserved." "\0" VALUE "OriginalFilename", "mom_kitchen.exe" "\0" - VALUE "ProductName", "mom_kitchen" "\0" + VALUE "ProductName", "小妈厨房" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 955ee30..fdc4384 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -65,6 +65,17 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message, case WM_FONTCHANGE: flutter_controller_->engine()->ReloadSystemFonts(); break; + case WM_XBUTTONUP: { + int button = GET_XBUTTON_WPARAM(wparam); + if (button == XBUTTON1) { + PostMessage(hwnd, WM_KEYDOWN, VK_BROWSER_BACK, 0); + PostMessage(hwnd, WM_KEYUP, VK_BROWSER_BACK, 0); + } else if (button == XBUTTON2) { + PostMessage(hwnd, WM_KEYDOWN, VK_BROWSER_FORWARD, 0); + PostMessage(hwnd, WM_KEYUP, VK_BROWSER_FORWARD, 0); + } + return TRUE; + } } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index 061d9f7..d6c40c4 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -26,8 +26,8 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); - Win32Window::Size size(1280, 720); - if (!window.Create(L"mom_kitchen", origin, size)) { + Win32Window::Size size(520, 800); + if (!window.Create(L"小妈厨房", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..5e64733 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp index 60608d0..72cf53f 100644 --- a/windows/runner/win32_window.cpp +++ b/windows/runner/win32_window.cpp @@ -88,19 +88,24 @@ WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; const wchar_t* WindowClassRegistrar::GetWindowClass() { if (!class_registered_) { - WNDCLASS window_class{}; + WNDCLASSEX window_class{}; + window_class.cbSize = sizeof(WNDCLASSEX); window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; window_class.style = CS_HREDRAW | CS_VREDRAW; window_class.cbClsExtra = 0; window_class.cbWndExtra = 0; window_class.hInstance = GetModuleHandle(nullptr); - window_class.hIcon = - LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hIcon = static_cast(LoadImage( + window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON, + 256, 256, LR_DEFAULTCOLOR)); + window_class.hIconSm = static_cast(LoadImage( + window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON, + 48, 48, LR_DEFAULTCOLOR)); window_class.hbrBackground = 0; window_class.lpszMenuName = nullptr; window_class.lpfnWndProc = Win32Window::WndProc; - RegisterClass(&window_class); + RegisterClassEx(&window_class); class_registered_ = true; } return kWindowClassName; @@ -134,16 +139,43 @@ bool Win32Window::Create(const std::wstring& title, UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); double scale_factor = dpi / 96.0; + int scaled_width = Scale(size.width, scale_factor); + int scaled_height = Scale(size.height, scale_factor); + + // Center window on screen + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(monitor, &monitor_info); + int screen_width = monitor_info.rcWork.right - monitor_info.rcWork.left; + int screen_height = monitor_info.rcWork.bottom - monitor_info.rcWork.top; + int pos_x = monitor_info.rcWork.left + (screen_width - scaled_width) / 2; + int pos_y = monitor_info.rcWork.top + (screen_height - scaled_height) / 2; + HWND window = CreateWindow( window_class, title.c_str(), WS_OVERLAPPEDWINDOW, - Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), - Scale(size.width, scale_factor), Scale(size.height, scale_factor), + pos_x, pos_y, + scaled_width, scaled_height, nullptr, nullptr, GetModuleHandle(nullptr), this); if (!window) { return false; } + // Set high-resolution icons for taskbar and title bar + HINSTANCE hInst = GetModuleHandle(nullptr); + HICON hIconLarge = static_cast(LoadImage( + hInst, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON, + 256, 256, LR_DEFAULTCOLOR)); + HICON hIconSmall = static_cast(LoadImage( + hInst, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON, + 48, 48, LR_DEFAULTCOLOR)); + if (hIconLarge) { + SendMessage(window, WM_SETICON, ICON_BIG, reinterpret_cast(hIconLarge)); + } + if (hIconSmall) { + SendMessage(window, WM_SETICON, ICON_SMALL, reinterpret_cast(hIconSmall)); + } + UpdateTheme(window); return OnCreate(); @@ -195,6 +227,21 @@ Win32Window::MessageHandler(HWND hwnd, SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + // Reload icons at new DPI + HINSTANCE hInst = GetModuleHandle(nullptr); + HICON hIconLarge = static_cast(LoadImage( + hInst, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON, + 256, 256, LR_DEFAULTCOLOR)); + HICON hIconSmall = static_cast(LoadImage( + hInst, MAKEINTRESOURCE(IDI_APP_ICON), IMAGE_ICON, + 48, 48, LR_DEFAULTCOLOR)); + if (hIconLarge) { + SendMessage(hwnd, WM_SETICON, ICON_BIG, reinterpret_cast(hIconLarge)); + } + if (hIconSmall) { + SendMessage(hwnd, WM_SETICON, ICON_SMALL, reinterpret_cast(hIconSmall)); + } + return 0; } case WM_SIZE: { @@ -207,6 +254,17 @@ Win32Window::MessageHandler(HWND hwnd, return 0; } + case WM_GETMINMAXINFO: { + // Limit minimum window size (480x700 logical pixels) + auto info = reinterpret_cast(lparam); + UINT dpi = FlutterDesktopGetDpiForMonitor( + MonitorFromWindow(window_handle_, MONITOR_DEFAULTTONEAREST)); + double scale_factor = dpi / 96.0; + info->ptMinTrackSize.x = Scale(480, scale_factor); + info->ptMinTrackSize.y = Scale(600, scale_factor); + return 0; + } + case WM_ACTIVATE: if (child_content_ != nullptr) { SetFocus(child_content_);