From 70d6ad6c421ad502e8141935b1dc97b789eb5939 Mon Sep 17 00:00:00 2001 From: xuchang Date: Thu, 21 Sep 2023 15:52:26 +0800 Subject: [PATCH] =?UTF-8?q?IssueNo:=20#I83A2G=20=E8=A1=A5=E5=85=85ohos?= =?UTF-8?q?=E7=9A=84embedding=E5=B1=82=E5=AE=9E=E7=8E=B0=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=20Description:=201=E3=80=81=E8=A1=A5=E5=85=85ohos=E7=9A=84embe?= =?UTF-8?q?dding=E5=B1=82=E5=AE=9E=E7=8E=B0=E4=BB=A3=E7=A0=81=EF=BC=9B=202?= =?UTF-8?q?=E3=80=81=E8=A1=A5=E5=85=85readme=EF=BC=8C=E6=96=B0=E5=A2=9Eemb?= =?UTF-8?q?edding=E9=83=A8=E5=88=86=E6=9E=84=E5=BB=BA=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E3=80=82=20Sig:=20OpenHarmony-SIG/flutter-engine=20Feature=20o?= =?UTF-8?q?r=20Bugfix:=20Feature=20Binary=20Source:=20No?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: xuchang --- OAT.xml | 3 + README.en.md | 51 +- README.md | 14 + .../ohos/flutter_embedding/AppScope/app.json5 | 25 + .../resources/base/element/string.json | 8 + .../resources/base/media/app_icon.png | Bin 0 -> 6790 bytes .../flutter_embedding/build-profile.json5 | 34 + .../flutter/build-profile.json5 | 26 + .../flutter_embedding/flutter/hvigorfile.ts | 17 + .../ohos/flutter_embedding/flutter/index.ets | 18 + .../flutter/oh-package.json5 | 27 + .../src/main/cpp/types/libflutter/index.d.ts | 81 ++ .../cpp/types/libflutter/index_actual.d.ts | 267 ++++++ .../cpp/types/libflutter/oh-package.json5 | 21 + .../flutter/src/main/ets/FlutterInjector.ts | 49 ++ .../main/ets/component/FlutterComponent.ets | 32 + .../ets/embedding/engine/FlutterEngine.ts | 263 ++++++ .../embedding/engine/FlutterEngineCache.ts | 64 ++ .../engine/FlutterEngineConnectionRegistry.ts | 267 ++++++ .../embedding/engine/FlutterEngineGroup.ts | 152 ++++ .../engine/FlutterEngineGroupCache.ts | 42 + .../main/ets/embedding/engine/FlutterNapi.ts | 359 ++++++++ .../ets/embedding/engine/FlutterShellArgs.ts | 86 ++ .../ets/embedding/engine/dart/DartExecutor.ts | 380 ++++++++ .../embedding/engine/dart/DartMessenger.ts | 235 +++++ .../engine/dart/PlatformMessageHandler.ts | 22 + .../engine/loader/ApplicationInfoLoader.ts | 24 + .../engine/loader/FlutterApplicationInfo.ts | 50 ++ .../embedding/engine/loader/FlutterLoader.ts | 221 +++++ .../embedding/engine/plugins/FlutterPlugin.ts | 111 +++ .../engine/plugins/PluginRegistry.ts | 73 ++ .../engine/plugins/ability/AbilityAware.ts | 75 ++ .../plugins/ability/AbilityControlSurface.ts | 27 + .../plugins/ability/AbilityPluginBinding.ts | 84 ++ .../systemchannels/AccessibilityChannel.ts | 119 +++ .../engine/systemchannels/KeyEventChannel.ts | 75 ++ .../engine/systemchannels/LifecycleChannel.ts | 105 +++ .../systemchannels/LocalizationChannel.ts | 70 ++ .../systemchannels/MouseCursorChannel.ts | 85 ++ .../systemchannels/NavigationChannel.ts | 62 ++ .../engine/systemchannels/PlatformChannel.ts | 485 +++++++++++ .../systemchannels/RestorationChannel.ts | 163 ++++ .../engine/systemchannels/SettingsChannel.ts | 89 ++ .../engine/systemchannels/SystemChannel.ts | 39 + .../engine/systemchannels/TestChannel.ts | 36 + .../engine/systemchannels/TextInputChannel.ts | 329 +++++++ .../embedding/ohos/ExclusiveAppComponent.ts | 32 + .../main/ets/embedding/ohos/FlutterAbility.ts | 401 +++++++++ .../embedding/ohos/FlutterAbilityDelegate.ts | 434 ++++++++++ .../ohos/FlutterAbilityLaunchConfigs.ts | 46 + .../ohos/FlutterEngineConfigurator.ts | 23 + .../embedding/ohos/FlutterEngineProvider.ts | 21 + .../main/ets/embedding/ohos/FlutterPage.ets | 34 + .../src/main/ets/embedding/ohos/Settings.ts | 35 + .../src/main/ets/plugin/PlatformPlugin.ts | 298 +++++++ .../ets/plugin/common/BasicMessageChannel.ts | 170 ++++ .../src/main/ets/plugin/common/BinaryCodec.ts | 49 ++ .../main/ets/plugin/common/BinaryMessenger.ts | 159 ++++ .../ets/plugin/common/FlutterException.ts | 28 + .../ets/plugin/common/JSONMessageCodec.ts | 52 ++ .../main/ets/plugin/common/JSONMethodCodec.ts | 96 +++ .../main/ets/plugin/common/MessageCodec.ts | 30 + .../src/main/ets/plugin/common/MethodCall.ts | 59 ++ .../main/ets/plugin/common/MethodChannel.ts | 214 +++++ .../src/main/ets/plugin/common/MethodCodec.ts | 87 ++ .../main/ets/plugin/common/PluginRegistry.ts | 14 + .../ets/plugin/common/StandardMessageCodec.ts | 310 +++++++ .../ets/plugin/common/StandardMethodCodec.ts | 116 +++ .../src/main/ets/plugin/common/StringCodec.ts | 42 + .../plugin/editing/ListenableEditingState.ts | 266 ++++++ .../ets/plugin/editing/TextEditingDelta.ts | 79 ++ .../ets/plugin/editing/TextInputPlugin.ts | 250 ++++++ .../plugin/localization/LocalizationPlugin.ts | 63 ++ .../ets/plugin/mouse/MouseCursorPlugin.ts | 129 +++ .../flutter/src/main/ets/util/ByteBuffer.ts | 814 ++++++++++++++++++ .../flutter/src/main/ets/util/Log.ts | 122 +++ .../src/main/ets/util/MessageChannelUtils.ts | 25 + .../flutter/src/main/ets/util/PathUtils.ts | 31 + .../flutter/src/main/ets/util/StringUtils.ts | 39 + .../flutter/src/main/ets/util/ToolUtils.ts | 24 + .../flutter/src/main/ets/util/TraceSection.ts | 39 + .../src/main/ets/view/AccessibilityBridge.ts | 45 + .../ets/view/FlutterCallbackInformation.ts | 39 + .../flutter/src/main/module.json5 | 24 + .../hvigor/hvigor-config.json5 | 21 + .../hvigor/hvigor-wrapper.js | 2 + .../ohos/flutter_embedding/hvigorfile.ts | 17 + shell/platform/ohos/flutter_embedding/hvigorw | 48 ++ .../ohos/flutter_embedding/hvigorw.bat | 64 ++ .../ohos/flutter_embedding/local.properties | 23 + .../flutter_embedding/oh-package-lock.json5 | 28 + .../ohos/flutter_embedding/oh-package.json5 | 28 + 92 files changed, 9817 insertions(+), 18 deletions(-) create mode 100755 shell/platform/ohos/flutter_embedding/AppScope/app.json5 create mode 100755 shell/platform/ohos/flutter_embedding/AppScope/resources/base/element/string.json create mode 100755 shell/platform/ohos/flutter_embedding/AppScope/resources/base/media/app_icon.png create mode 100755 shell/platform/ohos/flutter_embedding/build-profile.json5 create mode 100644 shell/platform/ohos/flutter_embedding/flutter/build-profile.json5 create mode 100644 shell/platform/ohos/flutter_embedding/flutter/hvigorfile.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/index.ets create mode 100644 shell/platform/ohos/flutter_embedding/flutter/oh-package.json5 create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index.d.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index_actual.d.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/oh-package.json5 create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/FlutterInjector.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/component/FlutterComponent.ets create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngine.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineCache.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroup.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroupCache.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterShellArgs.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartExecutor.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterLoader.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/FlutterPlugin.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/PluginRegistry.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SystemChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TestChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/ExclusiveAppComponent.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityDelegate.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineProvider.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterPage.ets create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/Settings.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/PlatformPlugin.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BasicMessageChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryMessenger.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/FlutterException.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMessageCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMethodCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MessageCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCall.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodChannel.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/PluginRegistry.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMessageCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMethodCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StringCodec.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/ListenableEditingState.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextEditingDelta.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextInputPlugin.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/localization/LocalizationPlugin.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/mouse/MouseCursorPlugin.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ByteBuffer.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/Log.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/MessageChannelUtils.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/PathUtils.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/StringUtils.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ToolUtils.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/TraceSection.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterCallbackInformation.ts create mode 100644 shell/platform/ohos/flutter_embedding/flutter/src/main/module.json5 create mode 100755 shell/platform/ohos/flutter_embedding/hvigor/hvigor-config.json5 create mode 100755 shell/platform/ohos/flutter_embedding/hvigor/hvigor-wrapper.js create mode 100755 shell/platform/ohos/flutter_embedding/hvigorfile.ts create mode 100755 shell/platform/ohos/flutter_embedding/hvigorw create mode 100755 shell/platform/ohos/flutter_embedding/hvigorw.bat create mode 100755 shell/platform/ohos/flutter_embedding/local.properties create mode 100755 shell/platform/ohos/flutter_embedding/oh-package-lock.json5 create mode 100755 shell/platform/ohos/flutter_embedding/oh-package.json5 diff --git a/OAT.xml b/OAT.xml index cf0ea9c1cd..4d0f4aafc4 100644 --- a/OAT.xml +++ b/OAT.xml @@ -98,6 +98,9 @@ used to filter file path. + + + diff --git a/README.en.md b/README.en.md index 9b9ebdfa6c..fd64b82399 100644 --- a/README.en.md +++ b/README.en.md @@ -1,37 +1,52 @@ Flutter Engine -=============== +============== -Source of original warehouse: https://github.com/flutter/engine +Original warehouse source: https://github.com/flutter/engine ## Warehouse description: -This repository is based on the extension of flutter's official engine repository, which can build flutter engine programs that support running on OpenHarmony devices. +This warehouse is based on the extension of Flutter's official engine warehouse and can build a Flutter engine program that supports running on OpenHarmony devices. ## Build instructions: * Build environment: -1. Currently only supports building under linux; +1. Currently only supports building under Linux; 2. Please ensure that the current build environment can access the allowed_hosts list configured in DEPS. -* Build steps: -1. Refer to https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment to configure the build environment under linux; +* Building steps: +1. Refer to the instructions at https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment to configure the build environment under Linux; -2. Obtain the source code, create an empty folder engine, create a new .gclient file in the engine, and edit the file: +2. Get the source code, create an empty folder engine, create a new .gclient file in the engine, and edit the file: ``` solutions = [ - { - "managed": False, - "name": "src/flutter", - "url": "git@gitee.com:openharmony-sig/flutter_engine.git", - "custom_deps": {}, - "deps_file": "DEPS", - "safesync_url": "", - }, + { + "managed": False, + "name": "src/flutter", + "url": "git@gitee.com:openharmony-sig/flutter_engine.git", + "custom_deps": {}, + "deps_file": "DEPS", + "safesync_url": "", + }, ] ``` -3. In the engine directory, execute 'gclient sync' command; here, the engine source code, the official packages warehouse, and the ohos_setup task will be executed; +3. In the engine directory, execute gclient sync; here the engine source code, official packages warehouse will be synchronized, and the ohos_setup task will be executed; -4. From the daily build at http://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist, download ohos-sdk-full, create a new folder 'ndk/4.0' in the engine root directory, and decompress 'ohos-sdk-all' files in the 'native' folder in the full sdk are transferred to the 'ndk/4.0' folder; +4. From http://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist, download ohos-sdk-full, create a new folder ndk/4.0 in the engine root directory, and unzip ohos-sdk- Move all files in the native folder in full sdk to the ndk/4.0 folder; -5. In the engine directory, execute 'make' command to start building the flutter engine that supports ohos devices. \ No newline at end of file +5. In the engine directory, execute make to start building the flutter engine that supports ohos devices. + + +## Embedding layer code construction guide + +1. Edit shell/platform/ohos/flutter_embedding/local.properties, + sdk.dir=\ + nodejs.dir=\ + +2. In the shell/platform/ohos/flutter_embedding directory, execute +``` +./hvigorw --mode module -p module=flutter@default -p product=default assembleHar +``` + + +3. The har file output path is: shell/platform/ohos/flutter_embedding/flutter/build \ No newline at end of file diff --git a/README.md b/README.md index fd6857e32a..5f1dcb550f 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,17 @@ solutions = [ 5. engine目录,执行make,既可以开始构建支持ohos设备的flutter engine。 + +## embedding层代码构建指导 + +1. 编辑shell/platform/ohos/flutter_embedding/local.properties, + sdk.dir=\ + nodejs.dir=\ + +2. 在shell/platform/ohos/flutter_embedding目录下,执行 +``` +./hvigorw --mode module -p module=flutter@default -p product=default assembleHar +``` + + +3. har文件输出路径为:shell/platform/ohos/flutter_embedding/flutter/build \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/AppScope/app.json5 b/shell/platform/ohos/flutter_embedding/AppScope/app.json5 new file mode 100755 index 0000000000..0b4a7d6f8a --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/AppScope/app.json5 @@ -0,0 +1,25 @@ +/* +* 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. +*/ + +{ + "app": { + "bundleName": "com.example.config", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/shell/platform/ohos/flutter_embedding/AppScope/resources/base/element/string.json b/shell/platform/ohos/flutter_embedding/AppScope/resources/base/element/string.json new file mode 100755 index 0000000000..816db6fc05 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "config" + } + ] +} diff --git a/shell/platform/ohos/flutter_embedding/AppScope/resources/base/media/app_icon.png b/shell/platform/ohos/flutter_embedding/AppScope/resources/base/media/app_icon.png new file mode 100755 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c GIT binary patch literal 6790 zcmX|G1ymHk)?T_}Vd;>R?p|tHQo6fg38|$UVM!6BLrPFWk?s;$LOP{GmJpBl$qoSA!PUg~PA65-S00{{S`XKG6NkG0RgjEntPrmV+?0|00mu7;+5 zrdpa{2QLqPJ4Y{j7=Mrl{BaxrkdY69+c~(w{Fv-v&aR%aEI&JYSeRTLWm!zbv;?)_ ziZB;fwGbbeL5Q}YLx`J$lp~A09KK8t_z}PZ=4ZzgdeKtgoc+o5EvN9A1K1_<>M?MBqb#!ASf&# zEX?<)!RH(7>1P+j=jqG(58}TVN-$psA6K}atCuI!KTJD&FMmH-78ZejBm)0qc{ESp z|LuG1{QnBUJRg_E=h1#XMWt2%fcoN@l7eAS!Es?Q+;XsRNPhiiE=@AqlLkJzF`O18 zbsbSmKN=aaq8k3NFYZfDWpKmM!coBU0(XnL8R{4=i|wi{!uWYM2je{U{B*K2PVdu&=E zTq*-XsEsJ$u5H4g6DIm2Y!DN`>^v|AqlwuCD;w45K0@eqauiqWf7l&o)+YLHm~|L~ z7$0v5mkobriU!H<@mVJHLlmQqzQ3d6Rh_-|%Yy2li*tHO>_vcnuZ7OR_xkAIuIU&x z-|8Y0wj|6|a6_I(v91y%k_kNw6pnkNdxjqG8!%Vz_d%c_!X+6-;1`GC9_FpjoHev5fEV7RhJ>r=mh-jp$fqbqRJ=obwdgLDVP5+s zy1=_DWG0Y-Jb3t^WXmkr(d9~08k-|#Ly zaNOmT(^9tIb&eb4%CzIT zAm3CUtWSr1t4?h1kk#NBi{U|pJslvME{q|_eS^3En>SOqSxyuN1x;Is@8~m?*>}** znrRFArP!K_52RpX*&JHMR<^lVdm8ypJ}0R(SD(51j;6@ni$6bQ+2XL+R^|NnSp5}(kzvMZ^(@4fD_{QVu$(&K6H|C37TG1Am9Re{<<3gd zh@`>;BqkXMW&p0T6rt|iB$)~CvFe(XC)F9WgAZn*0@t$oZo;!*}r@_`h?KKH&6A@3= zISXoQB+~`op>NP-buiA*^0n{@i{_?MRG)&k)c)k_F+-2Lud!S9pc+i`s74NpBCaGF zXN+pHkubw*msGBTY27BKHv)RRh3;nMg4&$fD_6X9Vt~;_4D+5XPH~#Kn-yjcy!$}1 zigv#FNY>TqMhtIBb@UoF!cE~Q8~;!Pek>SQQwHnHuWKoVBosAiOr}q>!>aE*Krc)V zBUMEcJ5NU0g8}-h6i1zpMY9>m4ne?=U2~`w7K7Q0gB_=p@$5K7p6}thw z-~3dMj?YNX2X$lZ+7ngQ$=s}3mizNN@kE%OtB)?c&i~2L55z8^=yz;xMHLmlY>&Q# zJj?!)M#q_SyfkQh)k?j8IfLtB)ZCp|*vf4_B zos?73yd^h-Ac+;?E4*bpf=o*^3x3-`TVjbY4n6!EN10K6o@fxdyps05Vo3PU)otB} z`3kR+2w7_C#8Z!q`J)p{Vh!+m9-UP!$STp+Hb}}#@#_u^SsUQg<}59< zTvH3%XS4G+6FF^(m6bVF&nSUIXcl;nw{=H$%fgeJ>CgDYiLdpDXr{;-AnG z8dvcrHYVMI&`R6;GWekI@Ir3!uo)oz4^{6q0m^}@f2tM9&=YHNi6-?rh0-{+k@cQm zdp`g#YdQn%MDVg2GR>wZ`n2<0l4)9nx1Wfr&!Dvz=bPwU!h2S?ez6MVc5APE4-xLB zi&W9Q8k2@0w!C53g?iAIQ}~p*3O(@zja6KQ=M3zfW*_6o5SwR-)6VBh~m7{^-=MC-owYH5-u40a}a0liho3QZZ5L{bS_xM1)4}19)zTU$$MY zq3eZML1WC{K%YFd`Be0M-rkO^l?h{kM{$2oK1*A@HVJ57*yhDkUF!2WZ&oA4Y-sK( zCY69%#`mBCi6>6uw(x4gbFaP0+FD*JKJ-q!F1E?vLJ+d35!I5d7@^eU?(CS|C^tmI5?lv@s{{*|1F zFg|OzNpZ0hxljdjaW%45O0MOttRrd(Z?h{HYbB-KFUx&9GfFL3b8NwZ$zNu)WbBD` zYkj$^UB5%3Pj1MDr>S2Ejr9pUcgA!;ZG!@{uAy12)vG=*^9-|dNQBc8&`oxBlU~#y zs!anJX&T?57Jdr^sb>e+V`MVfY>Y0ESg7MG<7W0g&bR-ZYzzZ%2H&Etcp zcd6QeXO1D!5A#zM0lx*GH}`M)2~ZFLE;sP^RSB5wVMNfiZXPd(cmO>j=OSA3`o5r& zna(|^jGXbdN7PK)U8b7^zYtYkkeb%<%F~=OqB~kXMQkq}ii|skh@WSRt>5za;cjP0 zZ~nD%6)wzedqE}BMLt~qKwlvTr33))#uP~xyw#*Eaa|DbMQ_%mG0U8numf8)0DX`r zRoG2bM;#g|p-8gWnwRV5SCW0tLjLO&9Z?K>FImeIxlGUgo0Zk`9Qzhj1eco~7XZy+hXc@YF&ZQ=? zn*^1O56yK^x{y}q`j7}blGCx%dydV!c7)g~tJzmHhV=W~jbWRRR{1<^oDK+1clprm zz$eCy7y9+?{E|YgkW~}}iB#I4XoJ*xr8R?i_Hv$=Cof5bo-Nj~f`-DLebH}&0% zfQj9@WGd4;N~Y?mzQsHJTJq6!Qzl^-vwol(+fMt#Pl=Wh#lI5Vmu@QM0=_r+1wHt` z+8WZ~c2}KQQ+q)~2Ki77QvV&`xb|xVcTms99&cD$Zz4+-^R4kvUBxG8gDk7Y`K*)JZ^2rL(+ZWV~%W(@6 z)0bPArG#BROa_PHs~&WplQ_UIrpd)1N1QGPfv!J(Z9jNT#i%H?CE6|pPZb9hJ1JW4 z^q;ft#!HRNV0YgPojzIYT`8LuET2rUe-J|c!9l4`^*;4WtY@Ew@pL>wkjmMgGfN7 ze}}GtmU0@<_#08~I-Suk=^*9GLW=H4xhsml;vAV{%hy5Eegl@!6qKqbG024%n2HHw zCc@ivW_$@5ZoHP70(7D+(`PvgjW1Pd`wsiuv-aCukMrafwDm)B!xXVy*j2opohhoU zcJz%ADmj>i3`-3-$7nQKBQQuGY;2Qt&+(L~C>vSGFj5{Mlv?T_^dql;{zkpe4R1}R z%XfZyQ}wr*sr>jrKgm*PWLjuVc%6&&`Kbf1SuFpHPN&>W)$GmqC;pIoBC`=4-hPY8 zT*>%I2fP}vGW;R=^!1be?ta2UQd2>alOFFbVl;(SQJ4Jk#)4Z0^wpWEVvY4=vyDk@ zqlModi@iVPMC+{?rm=4(n+<;|lmUO@UKYA>EPTS~AndtK^Wy^%#3<;(dQdk3WaUkRtzSMC9}7x2||CNpF#(3T4C)@ z$~RWs`BNABKX|{cmBt>Q=&gkXl&x!!NK_%5hW0LS)Z4PB>%sV?F-{Wyj#s7W%$F{D zXdK^Fp3wvy+48+GP6F_|^PCRx=ddcTO3sG;B23A49~Qaw31SZ0Rc~`r4qqt%#OGW{ zCA_(LG5^N>yzUn&kAgVmxb=EA8s&tBXC}S1CZ(KoW)(%^JjLTPo^fs`Va;`=YlVPgmB$!yB}<(4ym6OeZ3xAJJ#;)2+B%p3P1Wt+d$eo`vz`T zXfUP2))kBDPoscH;Jc7I3NU<({|@wM$&GaDt`n7WLgIY3IA7A6-_R?z8N3mz|}*i z(zl5ot--Oq@f2-nv{X(ujT2T(k1vY_qh93pK@>H-qc%2Xta)IP0Q%zt%bqYgI`o!wv!0QerB`nCN^1n|@$sVOQ!V0teVG!I z_fD%JvfDeT1cK#-{o6Gv7}& zY0#NWin~kVaf$aufV&;63Hbs|`QVZWpDX6IMk1Hj2G}fiH9e-^6u2zf^FIr^BwD<6zjw63+{yUe8PUFvk8v{sJ=R{d#`O!sz`Q13~< zPT$JS(w=yQfU2`zPCNfSw=&zup@DXc(98afjhv@1w_f!m2Z>rMJ19AB&dB%P#Ls3b z=lK7OILM+SQ&VEd=1GN6o&>YVVtIzoZ%=Z_SdqJN2}E43{bE`>w+A;=y->@^k{oCC z$F*WTY&?34;kfyFV?b*Xb1Pq`Z=%OgwEg)Rz)tx=`f%5#w_INP=x&z5!jI;#;N$ma zhO)+MDm;SxOEVL15; zGq(v2pL3&P1Sl)8P*;G-fd{l1QJsv@e@d8)1PK4w2m*M%V3j-V~L^$i|&C@b?D?9tfwE{B^}Z$k8e5FmQ>v7Xz)sG32g9t}YBt zyR$+*_00RmPx+0mW+vVG4mxd(n$(eQf3-w>JPl2UJpafrPaL5@2j}%{VE-) zBI%6Qpj*dsdH<;g!S!avA~bv^0E+ zfyJbSjPb+j;J52U)<|cIcntQBI2T#>2;tOxu{%D?kML476AErF(qN9hPva5Nkc@BF zC-tLF@3ZFb%Kpj)M<{)x*l|*Ia@ECeXo2E4h2f!aV=cHAhi_E_mfUth(sM4^hJq7B zQsGWqdZUm9S%F`$nQ*_#NcuD`&)Ek%_s{&^78{9Hm ztri&rYLOxgFdG>O@+XHy z9#;|&vBCPXH5Mon^I`jSuR$&~ZWtyB67ujzFSj!51>#C}C17~TffQ{c-!QFQkTQ%! zIR^b1`zHx|*1GU?tbBx23weFLz5H?y_Q%N&t$}k?w+``2A=aotj0;2v$~AL z{scF-cL{wsdrmPvf#a9OHyYLcwQD4Kcm)`LLwMh4WT~p29f7M!iafJSU`IV}QY5Wa z(n44-9oA}?J{a+ah*@31WTs#&J#o1`H98#6IQf;Wv0N_!);f&9g7o-k(lW5rWnDUR zQBFIRG+X=6NnsI@mxnwm;tf5;_Uxg?jZ8m-m0}&6+DA!qam(p$mN5R})yA_7m$q@| zFEd|dpS595rxQr-n#GjI5i-AhnUE>Cr;jpCqSrD~EwK_DqI^7%3#p5)%T_od!t3SOmH9MyXeeGO2(UQL;ax|x?Ncixmeo1=$ z{-);Au{*tfzOG?KQ~K|ak8-HQ?`Pekhe2WM(8s{xv-p>Zmu_6{G!-oE$7$mY`MOJorI=+mMx?H;`pr!;fVYz?5~yXBACruWB`Ph zZM}90_<^OBxIhyZ9BW$`>6JvO;%VFpqVr8|7t3~AmxYak6?`Pp#c;**_SYmi`&z23 z`p6_~ePvH)C6x-G9$hgL=eVALq`-AiamN>!3~Lxw&{H(b{B(7xSRm6<3<{%{yXiH# zos5Rv1L+8fUKJLo%P>4I&$}y napiContext; + +export class napiContext { + onPageShow(); + + onPageHide(); +} + +/** + * 设置刷新率 + */ +export const nativeUpdateRefreshRate: ( + ate: number +) => {}; + +/** + * 初始化dart vm和flutter engine + */ +export const nativeInit: ( + context: common.Context, + args: Array, + bundlePath: string, + appStoragePath: string, + engineCachesPath: string, + initTimeMillis: number +) => void; + +export const nativeAttach: (napi: FlutterNapi) => number; + +export const nativeRunBundleAndSnapshotFromLibrary: ( + nativeShellHolderId: number, + bundlePath: string, + entrypointFunctionName: string, + pathToEntrypointFunction: string, + assetManager: resourceManager.ResourceManager, + entrypointArgs: Array +) => void; + +//Send a data-carrying response to a platform message received from Dart. +export const nativeInvokePlatformMessageResponseCallback: (nativeShellHolderId: number, responseId: number, message: ArrayBuffer, position: number) => void; + +// Send an empty response to a platform message received from Dart. +export const nativeInvokePlatformMessageEmptyResponseCallback: (nativeShellHolderId: number, responseId: number) => void; + +// Send a data-carrying platform message to Dart. +export const nativeDispatchPlatformMessage: (nativeShellHolderId: number, channel: String, message: ArrayBuffer, position: number, responseId: number) => void; + +// Send an empty platform message to Dart. +export const nativeDispatchEmptyPlatformMessage: (nativeShellHolderId: number, channel: String, responseId: number) => void; + +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; + +export const nativeImageDecodeCallback: (width: number, height: number, imageGeneratorPointer: number, pixelMap : image.PixelMap) => void; + +export const nativeGetSystemLanguages: (nativeShellHolderId: number, languages: Array) => void; \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index_actual.d.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index_actual.d.ts new file mode 100644 index 0000000000..f74af44a9d --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/index_actual.d.ts @@ -0,0 +1,267 @@ +/* +* 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 FlutterNapi from '../../../ets/embedding/engine/FlutterNapi'; +import image from '@ohos.multimedia.image'; + +/** + * 设置刷新率 + */ +export const nativeUpdateRefreshRate: ( + ate: number +) => void; + +/** + * 初始化dart vm和flutter engine + */ +export const nativeInit: ( + context: common.Context, + args: Array, + bundlePath: string, + appStoragePath: string, + engineCachesPath: string, + initTimeMillis: number +) => void; + + +/** + * 加载dart工程构建产物 + */ +export const nativeRunBundleAndSnapshotFromLibrary: ( + bundlePath: string, + entrypointFunctionName: string, + pathToEntrypointFunction: string, + assetManager: resourceManager.ResourceManager, + entrypointArgs: Array +) => void; + +/** + * 初始化SkFontMgr::RefDefault(),skia引擎文字管理初始化 + */ +export const nativePrefetchDefaultFontManager: () => void; + +/** + * 返回是否支持软件绘制 + */ +export const nativeGetIsSoftwareRenderingEnabled: () => boolean; + +/** + * attach flutterNapi实例给到 native engine,这个支持rkts到flutter平台的无关引擎之间的通信。 + * attach只需要执行一次 + */ +export const nativeAttach: (flutterNapi: FlutterNapi) => number; + +/** + * 从当前的flutterNapi复制一个新的实例 + */ +export const nativeSpawn: ( + nativeSpawningShellId: number, + entrypointFunctionName: string, + pathToEntrypointFunction: string, + initialRoute: string, + entrypointArgs: Array +) => FlutterNapi; + +/** + * Detaches flutterNapi和engine之间的关联 + * 这个方法执行前提是flutterNapi已经和engine关联 + */ +export const nativeDestroy: ( + nativeShellHolderId: number +) => void; + +// 不需要实现,未使用到 +// export const nativeImageHeaderCallback: ( +// imageGeneratorPointer: number, +// width: number, +// height: number +// ) => void; + +/** + * 不需要实现,c++层已有nativeSurface回调 + */ +// export const nativeSurfaceCreated: ( +// nativeShellHolderId: number +// ) => void; + + +/** + * 不需要实现,c++层已有nativeSurface回调 + */ +// export const nativeSurfaceWindowChanged: ( +// nativeShellHolderId: number +// ) => void; + + +/** + * 不需要实现,c++层已有nativeSurface回调 + */ +// export const nativeSurfaceChanged: ( +// nativeShellHolderId: number, +// width: number, +// height: number +// ) => void; + +/** + * 不需要实现,c++层已有nativeSurface回调 + */ +// export const nativeSurfaceDestroyed: ( +// nativeShellHolderId: number +// ) => void; + +/** + * 把物理屏幕参数通知到native + */ +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; + +/** + * 设置能力参数 + */ +export const nativeSetAccessibilityFeatures: ( + nativeShellHolderId: number, + flags: number +) => void; + +/** + * 清除某个messageData + */ +export const nativeCleanupMessageData: ( + messageData: number +) => void; + +/** + * 发送一个空的PlatformMessage + */ +export const nativeDispatchEmptyPlatformMessage: ( + nativeShellHolderId: number, + channel: string, + responseId: number +) => void; + +/** + * 发送一个PlatformMessage + */ +export const nativeDispatchPlatformMessage: ( + nativeShellHolderId: number, + channel: string, + message: ArrayBuffer, + position: number, + responseId: number +) => void; + +/** + * 空的PlatformMessage响应回调 + */ +export const nativeInvokePlatformMessageEmptyResponseCallback: ( + nativeShellHolderId: number, + responseId: number +) => void; + +/** + * PlatformMessage响应回调 + */ +export const nativeInvokePlatformMessageResponseCallback: ( + nativeShellHolderId: number, + responseId: number, + message: ArrayBuffer, + position: number +) => void; + + +/** + * load一个合法的.so文件到dart vm + */ +export const nativeLoadDartDeferredLibrary: ( + nativeShellHolderId: number, + loadingUnitId: number, + searchPaths: Array +) => void; + +/** + * 设置ResourceManager和assetBundlePath到engine + */ +export const nativeUpdateOhosAssetManager: ( + nativeShellHolderId: number, + resourceManager: resourceManager.ResourceManager, + assetBundlePath: string +) => void; + +/** + * 加载动态库,或者dart库失败时的通知 + */ +export const nativeDeferredComponentInstallFailure: ( + loadingUnitId: number, + error: string, + isTransient: boolean +) => void; + +/** + * 从engine获取当前绘制pixelMap + */ +export const nativeGetPixelMap: () => image.PixelMap; + +/** + * 应用低内存警告 + */ +export const nativeNotifyLowMemoryWarning: ( + nativeShellHolderId: number +) => void; + +// ----- Start FlutterTextUtils Methods ---- +/** + * 下面的方法,从键盘输入中判断当前字符是否是emoji,实现优先级低 + */ +export const nativeFlutterTextUtilsIsEmoji: ( + codePoint: number +) => boolean; + +export const nativeFlutterTextUtilsIsEmojiModifier: ( + codePoint: number +) => boolean; + +export const nativeFlutterTextUtilsIsEmojiModifierBase: ( + codePoint: number +) => boolean; + +export const nativeFlutterTextUtilsIsVariationSelector: ( + codePoint: number +) => boolean; + +export const nativeFlutterTextUtilsIsRegionalIndicator: ( + codePoint: number +) => boolean; \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/oh-package.json5 b/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/oh-package.json5 new file mode 100644 index 0000000000..216ed0d720 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/cpp/types/libflutter/oh-package.json5 @@ -0,0 +1,21 @@ +/* +* 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. +*/ + +{ + "name": "libflutter.so", + "types": "./index.d.ts", + "version": "", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/FlutterInjector.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/FlutterInjector.ts new file mode 100644 index 0000000000..db12ccb09d --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/FlutterInjector.ts @@ -0,0 +1,49 @@ +/* +* 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 FlutterNapi from './embedding/engine/FlutterNapi'; +import FlutterLoader from './embedding/engine/loader/FlutterLoader'; + +/** + * flutter相关主要类的单例持有,帮助实现自身和其他类的实例化管理 + */ +export default class FlutterInjector { + private static instance: FlutterInjector; + + private flutterLoader: FlutterLoader; + private flutterNapi: FlutterNapi; + + static getInstance(): FlutterInjector { + if (this.instance == null) { + this.instance = new FlutterInjector(); + } + return this.instance; + } + /** + * 初始化 + */ + private constructor() { + this.flutterNapi = new FlutterNapi(); + this.flutterLoader = new FlutterLoader(this.flutterNapi); + } + + getFlutterLoader(): FlutterLoader { + return this.flutterLoader; + } + + getFlutterNapi(): FlutterNapi { + return this.flutterNapi; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/component/FlutterComponent.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/component/FlutterComponent.ets new file mode 100644 index 0000000000..07497e672b --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/component/FlutterComponent.ets @@ -0,0 +1,32 @@ +/* +* 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. +*/ + +/** + * 基础component,还未封装,看情况是否使用 + */ +@Component +export default struct FlutterComponent { + build() { + Row() { + Column() { + Text("xxx") + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngine.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngine.ts new file mode 100644 index 0000000000..48ffc063d8 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngine.ts @@ -0,0 +1,263 @@ +/* +* 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 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 FlutterEngineGroup from './FlutterEngineGroup'; +import SystemChannel from './systemchannels/SystemChannel'; +import MouseCursorChannel from './systemchannels/MouseCursorChannel'; +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'; + +const TAG = "FlutterEngine"; + +/** + * 操作FlutterEngin相关 + */ +export default class FlutterEngine implements EngineLifecycleListener{ + private engineLifecycleListeners = new Set(); + + dartExecutor: DartExecutor; + private flutterLoader: FlutterLoader; + private assetManager: resourceManager.ResourceManager; + //channel定义 + private lifecycleChannel: LifecycleChannel; + private navigationChannel: NavigationChannel; + private textInputChannel: TextInputChannel; + private testChannel: TestChannel; + private platformChannel: PlatformChannel; + private systemChannel: SystemChannel; + private mouseCursorChannel: MouseCursorChannel; + private restorationChannel: RestorationChannel; + + private accessibilityChannel: AccessibilityChannel; + private localeChannel: LocalizationChannel; + private flutterNapi: FlutterNapi; + private pluginRegistry: FlutterEngineConnectionRegistry; + private textInputPlugin: TextInputPlugin; + private localizationPlugin: LocalizationPlugin; + private settingsChannel: SettingsChannel; + + /** + * 需要初始化的工作: + * 1、初始化DartExecutor + * 2、初始化所有channel + * 3、初始化plugin + * 4、初始化flutterLoader + * 5、初始化flutterJNI + * 6、engineLifecycleListeners + */ + constructor(context: common.Context, flutterLoader: FlutterLoader, flutterNapi: FlutterNapi) { + 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; + } + + async init(context: common.Context, dartVmArgs: Array, automaticallyRegisterPlugins: boolean, + waitForRestorationData: boolean, group: FlutterEngineGroup) { + if (!this.flutterNapi.isAttached()) { + await this.flutterLoader.startInitialization(context) + this.flutterLoader.ensureInitializationComplete(dartVmArgs); + } + //channel初始化 + this.lifecycleChannel = new LifecycleChannel(this.dartExecutor); + this.navigationChannel = new NavigationChannel(this.dartExecutor); + this.textInputChannel = new TextInputChannel(this.dartExecutor); + this.testChannel = new TestChannel(this.dartExecutor); + this.platformChannel = new PlatformChannel(this.dartExecutor); + this.systemChannel = new SystemChannel(this.dartExecutor); + this.mouseCursorChannel = new MouseCursorChannel(this.dartExecutor); + 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); + + // It should typically be a fresh, unattached JNI. But on a spawned engine, the JNI 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.pluginRegistry = new FlutterEngineConnectionRegistry(context.getApplicationContext(), this, this.flutterLoader, group); + this.localizationPlugin.sendLocaleToFlutter(); + } + + private attachToNapi(): void { + Log.d(TAG, "Attaching to JNI."); + this.flutterNapi.attachToNative(); + + if (!this.isAttachedToNapi()) { + throw new Error("FlutterEngine failed to attach to its native Object reference."); + } + this.flutterNapi.setLocalizationPlugin(this.localizationPlugin); + } + + async spawn(context: common.Context, + dartEntrypoint: DartEntrypoint, + initialRoute: string, + dartEntrypointArgs: Array, + automaticallyRegisterPlugins: boolean, waitForRestorationData: boolean) { + if (!this.isAttachedToNapi()) { + throw new Error( + "Spawn can only be called on a fully constructed FlutterEngine"); + } + + const newFlutterJNI = + this.flutterNapi.spawn( + dartEntrypoint.dartEntrypointFunctionName, + dartEntrypoint.dartEntrypointLibrary, + initialRoute, + dartEntrypointArgs); + const flutterEngine = new FlutterEngine( + context, + null, + newFlutterJNI + ); + await flutterEngine.init(context, null, automaticallyRegisterPlugins, waitForRestorationData, null) + return flutterEngine + } + + private isAttachedToNapi(): boolean { + return this.flutterNapi.isAttached(); + } + + getLifecycleChannel(): LifecycleChannel { + return this.lifecycleChannel; + } + + getNavigationChannel(): NavigationChannel { + return this.navigationChannel; + } + + getTextInputChannel(): TextInputChannel { + return this.textInputChannel; + } + + getPlatformChannel(): PlatformChannel { + return this.platformChannel; + } + + getSystemChannel(): SystemChannel { + return this.systemChannel; + } + + getLocaleChannel(): LocalizationChannel { + return this.localeChannel; + } + + getMouseCursorChannel(): MouseCursorChannel { + return this.mouseCursorChannel; + } + + getFlutterNapi(): FlutterNapi { + return this.flutterNapi; + } + + getDartExecutor(): DartExecutor { + return this.dartExecutor + } + + getPlugins(): PluginRegistry { + return this.pluginRegistry; + } + + getAbilityControlSurface(): AbilityControlSurface { + return this.pluginRegistry; + } + + getSettingsChannel() { + return this.settingsChannel; + } + + onPreEngineRestart(): void { + + } + + onEngineWillDestroy(): void { + + } + + addEngineLifecycleListener(listener: EngineLifecycleListener): void { + this.engineLifecycleListeners.add(listener); + } + + removeEngineLifecycleListener(listener: EngineLifecycleListener): void { + this.engineLifecycleListeners.delete(listener); + } + + destroy(): void { + Log.d(TAG, "Destroying."); + this.engineLifecycleListeners.forEach(listener => listener.onEngineWillDestroy()) + this.flutterNapi.removeEngineLifecycleListener(this); + this.pluginRegistry.detachFromAbility() + //TODO + } + + getRestorationChannel(): RestorationChannel{ + return this.restorationChannel; + } + + getAccessibilityChannel(): AccessibilityChannel { + return this.accessibilityChannel; + } + + getLocalizationPlugin(): LocalizationPlugin { + return this.localizationPlugin; + } + + getSystemLanguages(): void { + return this.flutterNapi.getSystemLanguages(); + } +} + +export interface EngineLifecycleListener { + onPreEngineRestart(): void; + + onEngineWillDestroy(): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineCache.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineCache.ts new file mode 100644 index 0000000000..bfc0691b64 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineCache.ts @@ -0,0 +1,64 @@ +/* +* 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 FlutterEngine from "./FlutterEngine" + +export default class FlutterEngineCache { + private static instance : FlutterEngineCache; + private cachedEngines: Map = new Map(); + + static getInstance(): FlutterEngineCache { + if (this.instance == null) { + this.instance = new FlutterEngineCache(); + } + return this.instance; + } + /** + * 返回engineId对应的FlutterEngine是否存在 + */ + contains(engineId: String) : boolean { + return this.cachedEngines.has(engineId); + } + + /** + * 返回engineId对应的FlutterEngine + */ + get(engineId: String) : FlutterEngine { + return this.cachedEngines.get(engineId); + } + /** + * 将传入的FlutterEngine与engineId放在缓存中 + */ + put(engineId :String, engine: FlutterEngine): void { + if(engine != null) { + this.cachedEngines.set(engineId, engine); + } else { + this.cachedEngines.delete(engineId); + } + } + /** + * 移除engineId对应的FlutterEngine + */ + remove(engineId: String) : void { + this.put(engineId, null); + } + + /** + * 移除cachedEngines所有中所有的FlutterEngine + */ + clear():void { + this.cachedEngines.clear(); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ts new file mode 100644 index 0000000000..442d06295d --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineConnectionRegistry.ts @@ -0,0 +1,267 @@ +/* +* 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 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'; + +const TAG = "FlutterEngineCxnRegstry"; + +export default class FlutterEngineConnectionRegistry implements PluginRegistry, AbilityControlSurface { + // PluginRegistry + private plugins = new Map(); + + // Standard FlutterPlugin + private flutterEngine: FlutterEngine; + private pluginBinding: FlutterPluginBinding; + + // AbilityAware + private abilityAwarePlugins = new Map(); + + private exclusiveAbility: ExclusiveAppComponent; + private abilityPluginBinding: FlutterEngineAbilityPluginBinding; + + constructor(appContext: common.Context, flutterEngine: FlutterEngine, flutterLoader: FlutterLoader, group: FlutterEngineGroup) { + this.flutterEngine = flutterEngine; + this.pluginBinding = new FlutterPluginBinding(appContext, this.flutterEngine.getDartExecutor(), new DefaultFlutterAssets(flutterLoader), group); + } + + 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); + // 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 = plugin as any + this.abilityAwarePlugins.set(plugin.getUniqueClassName(), abilityAware); + if (this.isAttachedToAbility()) { + abilityAware.onAttachedToAbility(this.abilityPluginBinding); + } + } + } finally { + + } + } + + addList(plugins: Set): void { + plugins.forEach(plugin => this.add(plugin)) + } + + has(pluginClassName: string): boolean { + return this.plugins.has(pluginClassName); + } + + get(pluginClassName: string): FlutterPlugin { + return this.plugins.get(pluginClassName); + } + + remove(pluginClassName: string): void { + const plugin = this.plugins.get(pluginClassName); + if (plugin == null) { + return; + } + if (ToolUtils.implementsInterface(plugin, "onAttachedToAbility")) { + if (this.isAttachedToAbility()) { + const abilityAware = plugin as any + 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) + } + + removeList(pluginClassNames: Set): void { + pluginClassNames.forEach(plugin => this.remove(plugin)) + } + + removeAll(): void { + this.removeList(new Set(this.plugins.keys())); + this.plugins.clear(); + } + + private isAttachedToAbility(): boolean { + return this.exclusiveAbility != null; + } + + 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(),); + } + + detachFromAbility(): void { + if (this.isAttachedToAbility()) { + this.abilityAwarePlugins.forEach(abilityAware => abilityAware.onDetachedFromAbility()) + this.detachFromAbilityInternal(); + } else { + Log.e(TAG, "Attempted to detach plugins from an Ability when no Ability was attached."); + } + } + + onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { + this.abilityPluginBinding.onNewWant(want, launchParams); + } + + onWindowFocusChanged(hasFocus: boolean): void { + this.abilityPluginBinding.onWindowFocusChanged(hasFocus); + } + + onSaveState(reason: AbilityConstant.StateType, wantParam: { [key: string]: Object; }): AbilityConstant.OnSaveResult { + return this.abilityPluginBinding.onSaveState(reason, wantParam); + } + + private detachFromAppComponent(): void { + if (this.isAttachedToAbility()) { + this.detachFromAbility(); + } + } + + private attachToAbilityInternal(ability: UIAbility): void { + this.abilityPluginBinding = new FlutterEngineAbilityPluginBinding(ability); + //TODO set PlatformViewsController setSoftwareRendering attach + // Notify all AbilityAware plugins that they are now attached to a new Ability. + this.abilityAwarePlugins.forEach(abilityAware => abilityAware.onAttachedToAbility(this.abilityPluginBinding)); + } + + private detachFromAbilityInternal(): void { + // TODO Deactivate PlatformViewsController. detach + this.exclusiveAbility = null; + this.abilityPluginBinding = null; + } + + destroy(): void{ + this.detachFromAppComponent(); + // Remove all registered plugins. + this.removeAll(); + } +} + +class FlutterEngineAbilityPluginBinding implements AbilityPluginBinding { + private ability: UIAbility; + private onNewWantListeners = new HashSet(); + private onWindowFocusChangedListeners = new HashSet(); + private onSaveStateListeners = new HashSet(); + + constructor(ability: UIAbility) { + this.ability = ability; + + } + + getAbility(): UIAbility { + return this.ability; + } + + addOnNewWantListener(listener: NewWantListener): void { + this.onNewWantListeners.add(listener) + } + + removeOnNewWantListener(listener: NewWantListener): void { + this.onNewWantListeners.remove(listener) + } + + addOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void { + this.onWindowFocusChangedListeners.add(listener) + } + + removeOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void { + this.onWindowFocusChangedListeners.remove(listener) + } + + addOnSaveStateListener(listener: OnSaveStateListener) { + this.onSaveStateListeners.add(listener) + } + + removeOnSaveStateListener(listener: OnSaveStateListener) { + this.onSaveStateListeners.remove(listener) + } + + onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void { + this.onNewWantListeners.forEach((listener, key) => { + listener.onNewWant(want, launchParams) + }); + } + + onWindowFocusChanged(hasFocus: boolean): void { + this.onWindowFocusChangedListeners.forEach((listener, key) => { + listener.onWindowFocusChanged(hasFocus) + }); + } + + onSaveState(reason: AbilityConstant.StateType, wantParam: { [key: string]: Object; }): AbilityConstant.OnSaveResult { + this.onSaveStateListeners.forEach((listener, key) => { + listener.onSaveState(reason, wantParam) + }); + return AbilityConstant.OnSaveResult.ALL_AGREE; + } +} + +class DefaultFlutterAssets implements FlutterAssets { + private flutterLoader: FlutterLoader; + + constructor(flutterLoader: FlutterLoader) { + this.flutterLoader = flutterLoader; + } + + getAssetFilePathByName(assetFileName: string, packageName?: string): string { + return this.flutterLoader.getLookupKeyForAsset(assetFileName, packageName); + } + + getAssetFilePathBySubpath(assetSubpath: string, packageName?: string) { + return this.flutterLoader.getLookupKeyForAsset(assetSubpath, packageName); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroup.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroup.ts new file mode 100644 index 0000000000..e7765f56f9 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroup.ts @@ -0,0 +1,152 @@ +/* +* 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 FlutterEngine from "./FlutterEngine" +import common from '@ohos.app.ability.common' +import FlutterLoader from './loader/FlutterLoader' +import FlutterInjector from '../../FlutterInjector' +import { DartEntrypoint } from './dart/DartExecutor' + +export default class FlutterEngineGroup { + private activeEngines: Array = new Array(); + + constructor() { + + } + + async checkLoader(context: common.Context, args: Array) { + var loader: FlutterLoader = FlutterInjector.getInstance().getFlutterLoader(); + if (!loader.initialized) { + await loader.startInitialization(context.getApplicationContext()); + loader.ensureInitializationComplete(args); + } + } + + async createAndRunEngineByOptions(options: Options) { + let engine: FlutterEngine = null; + let context: common.Context = options.getContext(); + let dartEntrypoint: DartEntrypoint = options.getDartEntrypoint(); + let initialRoute: string = options.getInitialRoute(); + let dartEntrypointArgs: Array = options.getDartEntrypointArgs(); + //TODO:接入PlatformViewsController + let automaticallyRegisterPlugins: boolean = options.getAutomaticallyRegisterPlugins(); + let waitForRestorationData: boolean = options.getWaitForRestorationData(); + + if (dartEntrypoint == null) { + dartEntrypoint = DartEntrypoint.createDefault(); + } + + if (this.activeEngines.length == 0) { + engine = this.createEngine(context); + await engine.init(context, null, // String[]. The Dart VM has already started, this arguments will have no effect. + automaticallyRegisterPlugins, // boolean. + waitForRestorationData, // boolean. + this) + if (initialRoute != null) { + engine.getNavigationChannel().setInitialRoute(initialRoute); + } + } else { + engine = await this.activeEngines[0] + .spawn( + context, + dartEntrypoint, + initialRoute, + dartEntrypointArgs, + automaticallyRegisterPlugins, + waitForRestorationData); + } + this.activeEngines.push(engine); + + const engineToCleanUpOnDestroy = engine; + engine.addEngineLifecycleListener({ + onPreEngineRestart(): void { + // No-op. Not interested. + }, + onEngineWillDestroy(): void { + this.activeEngines.remove(engineToCleanUpOnDestroy); + } + }); + return engine; + } + + createEngine(context: common.Context): FlutterEngine { + return new FlutterEngine(context, null, null); + } +} + +export class Options { + private context: common.Context; + private dartEntrypoint: DartEntrypoint; + private initialRoute: string; + private dartEntrypointArgs: Array; + //TODO:声明成员platformViewsController:PlatformViewsController + private automaticallyRegisterPlugins: boolean = true; + private waitForRestorationData: boolean = false; + + constructor(context: common.Context) { + this.context = context; + } + + getContext(): any { + return this.context; + } + + getDartEntrypoint(): DartEntrypoint { + return this.dartEntrypoint; + } + + getInitialRoute(): string { + return this.initialRoute; + } + + getDartEntrypointArgs(): Array { + return this.dartEntrypointArgs; + } + + getAutomaticallyRegisterPlugins(): boolean { + return this.automaticallyRegisterPlugins; + } + + getWaitForRestorationData(): boolean { + return this.waitForRestorationData; + } + //TODO:实现getPlatformViewsController + + setDartEntrypoint(dartEntrypoint: DartEntrypoint): Options { + this.dartEntrypoint = dartEntrypoint; + return this; + } + + setInitialRoute(initialRoute: string): Options { + this.initialRoute = initialRoute; + return this; + } + + setDartEntrypointArgs(dartEntrypointArgs: Array): Options { + this.dartEntrypointArgs = dartEntrypointArgs; + return this; + } + + setAutomaticallyRegisterPlugins(automaticallyRegisterPlugins: boolean): Options { + this.automaticallyRegisterPlugins = automaticallyRegisterPlugins; + return this; + } + + setWaitForRestorationData(waitForRestorationData: boolean): Options { + this.waitForRestorationData = waitForRestorationData; + return this; + } + //TODO:实现setPlatformViewsController +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroupCache.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroupCache.ts new file mode 100644 index 0000000000..34b251069c --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterEngineGroupCache.ts @@ -0,0 +1,42 @@ +/* +* 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 FlutterEngineGroup from './FlutterEngineGroup'; + +export default class FlutterEngineGroupCache { + static readonly instance = new FlutterEngineGroupCache(); + + private cachedEngineGroups = new Map(); + + contains(engineGroupId: string): boolean { + return this.cachedEngineGroups.has(engineGroupId); + } + + get(engineGroupId: string): FlutterEngineGroup { + return this.cachedEngineGroups.get(engineGroupId); + } + + put(engineGroupId: string, engineGroup?: FlutterEngineGroup) { + if (engineGroup != null) { + this.cachedEngineGroups.set(engineGroupId, engineGroup); + } else { + this.cachedEngineGroups.delete(engineGroupId); + } + } + + clear(): void { + this.cachedEngineGroups.clear(); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ts new file mode 100644 index 0000000000..e074ff82ea --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterNapi.ts @@ -0,0 +1,359 @@ +/* +* 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 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 { Action } from '../../view/AccessibilityBridge' +import LocalizationPlugin from '../../plugin/localization/LocalizationPlugin'; +import i18n from '@ohos.i18n'; + +const TAG = "FlutterNapi"; + +enum ContextType { + APP_LIFECYCLE = 0, + JS_PAGE_LIFECYCLE, +} + +/** + * 提供arkTs的flutterNAPI接口 + */ +export default class FlutterNapi { + hasInit: boolean = false; + //是否已实现 + hasImplemented: boolean = false; + + nativeShellHolderId: number = null; + platformMessageHandler: PlatformMessageHandler; + private engineLifecycleListeners = new Set(); + accessibilityDelegate: AccessibilityDelegate; + localizationPlugin: LocalizationPlugin; + + /** + * 更新刷新率 + * @param rate + */ + updateRefreshRate(refreshRateFPS : number) { + flutter.nativeUpdateRefreshRate(refreshRateFPS); + } + + init(context: common.Context, + args: Array, + bundlePath: string, + appStoragePath: string, + engineCachesPath: string, + initTimeMillis: number) { + if (this.hasInit) { + throw Error("the engine has init"); + } + this.hasInit = true; + Log.w(TAG, "init: bundlePath=" + bundlePath + " appStoragePath=" + appStoragePath + " engineCachesPath=" + engineCachesPath + " args=" + JSON.stringify(args)); + flutter.nativeInit(context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); + } + + attachToNative(): void { + this.nativeShellHolderId = flutter.nativeAttach(this); + Log.w(TAG, "nativeShellHolderId=" + this.nativeShellHolderId); + } + + runBundleAndSnapshotFromLibrary( + bundlePath: string, + entrypointFunctionName: string, + pathToEntrypointFunction: string, + assetManager: resourceManager.ResourceManager, + entrypointArgs: Array) { + Log.w(TAG, "init: bundlePath=" + bundlePath + " entrypointFunctionName=" + entrypointFunctionName + " pathToEntrypointFunction=" + pathToEntrypointFunction + " entrypointArgs=" + JSON.stringify(entrypointArgs)) + flutter.nativeRunBundleAndSnapshotFromLibrary(this.nativeShellHolderId, bundlePath, entrypointFunctionName, pathToEntrypointFunction, assetManager, entrypointArgs); + }; + + /** + * 当前so方法是否都实现 + * @returns + */ + checkImplemented(methodName: string = ""): boolean { + if (!this.hasImplemented) { + Log.e(TAG, "this method has not implemented -> " + methodName) + } + return this.hasImplemented; + } + + setPlatformMessageHandler(platformMessageHandler: PlatformMessageHandler): void { + this.ensureRunningOnMainThread(); + this.platformMessageHandler = platformMessageHandler; + } + + private ensureAttachedToNative(): void { + if (this.nativeShellHolderId == null) { + throw new Error( + "Cannot execute operation because FlutterNapi is not attached to native."); + } + } + + private nativeNotifyLowMemoryWarning(nativeShellHolderId: number): void { + + } + + static nativeLookupCallbackInformation(handle: number): FlutterCallbackInformation { + return null; + } + + notifyLowMemoryWarning(): void { + this.ensureRunningOnMainThread(); + this.ensureAttachedToNative(); + this.nativeNotifyLowMemoryWarning(this.nativeShellHolderId); + } + + isAttached(): boolean { + return this.nativeShellHolderId != null; + } + + private ensureRunningOnMainThread(): void { + + } + + dispatchEmptyPlatformMessage(channel: String, responseId: number): void { + this.ensureRunningOnMainThread(); + if (this.isAttached()) { + 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 reply {@code message} from Android to Flutter over the given {@code channel}. */ + dispatchPlatformMessage(channel: String, message: ArrayBuffer, position: number, responseId: number): void { + this.ensureRunningOnMainThread(); + if (this.isAttached()) { + + const uintArrayBuff = new Uint8Array(message) + let text = '' + for (let i = 0; i < uintArrayBuff.byteLength; i++) { + text += uintArrayBuff[i] + ',' + } + Log.w(TAG, "message=" + message.byteLength + ",text=" + text); + 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); + } + } + + invokePlatformMessageEmptyResponseCallback(responseId: number): void { + if (this.isAttached()) { + 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); + } + } + + invokePlatformMessageResponseCallback(responseId: number, message: ArrayBuffer, position: number) { + if (this.isAttached()) { + 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); + } + } + + 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()) { + flutter.nativeSetViewportMetrics(this.nativeShellHolderId, devicePixelRatio, + physicalWidth, + physicalHeight, + physicalPaddingTop, + physicalPaddingRight, + physicalPaddingBottom, + physicalPaddingLeft, + physicalViewInsetTop, + physicalViewInsetRight, + physicalViewInsetBottom, + physicalViewInsetLeft, + systemGestureInsetTop, + systemGestureInsetRight, + systemGestureInsetBottom, + systemGestureInsetLeft, + physicalTouchSlop, + displayFeaturesBounds, + displayFeaturesType, + displayFeaturesState); + } + } + + spawn(entrypointFunctionName: string, pathToEntrypointFunction: string, initialRoute: string, entrypointArgs: Array): FlutterNapi { + return new FlutterNapi(); + } + + addEngineLifecycleListener(engineLifecycleListener: EngineLifecycleListener): void { + this.engineLifecycleListeners.add(engineLifecycleListener); + } + + removeEngineLifecycleListener(engineLifecycleListener: EngineLifecycleListener) { + this.engineLifecycleListeners.delete(engineLifecycleListener); + } + + //Called by native to respond to a platform message that we sent. + handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void { + Log.w(TAG, "called handlePlatformMessageResponse Response ID: " + replyId); + if (this.platformMessageHandler != null) { + this.platformMessageHandler.handlePlatformMessageResponse(replyId, reply); + } + } + + // Called by native on any thread. + handlePlatformMessage(channel: string, message: ArrayBuffer, replyId: number, messageData: number): void { + Log.w(TAG, "called handlePlatformMessage Channel: " + channel + ". Response ID: " + replyId); + if (this.platformMessageHandler != null) { + this.platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData); + } + } + + // Called by native to notify first Flutter frame rendered. + onFirstFrame(): void { + Log.d(TAG, "called onFirstFrame") + } + + // Called by native. + onPreEngineRestart(): void { + Log.d(TAG, "called onPreEngineRestart") + this.engineLifecycleListeners.forEach( listener => listener.onPreEngineRestart()); + } + + // /** Invoked by native to obtain the results of OHOS's locale resolution algorithm. */ + computePlatformResolvedLocale(strings: Array): Array { + Log.d(TAG, "called computePlatformResolvedLocale " + JSON.stringify(strings)) + return [] + } + + decodeImage(buffer: ArrayBuffer, imageGeneratorAddress: number): void { + Log.d(TAG, "called decodeImage=" + buffer.byteLength) + const imageSourceApi = image.createImageSource(buffer); + let tempPixelMap = null; + imageSourceApi.createPixelMap({ + desiredPixelFormat: image.PixelMapFormat.RGBA_8888 + }).then(pixelMap => { + Log.d(TAG, "called createPixelMap end " + pixelMap.getPixelBytesNumber()) + tempPixelMap = pixelMap + return pixelMap.getImageInfo() + }).then(imageInfo => { + Log.d(TAG, `nativeImageHeaderCallback width=${imageInfo.size.width} height=${imageInfo.size.height} imageGeneratorAddress=${imageGeneratorAddress}`) + flutter.nativeImageDecodeCallback(imageInfo.size.width, imageInfo.size.height, imageGeneratorAddress, tempPixelMap) + }).catch(error => { + Log.d(TAG, "decodeImage error=" + JSON.stringify(error)) + flutter.nativeImageDecodeCallback(0, 0, imageGeneratorAddress, null); + }) + } + + setSemanticsEnabled(enabled: boolean, responseId: number): void { + if (this.isAttached()) { + this.nativeSetSemanticsEnabled(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); + } + } + + // Send an empty response to a platform message received from Dart. + nativeSetSemanticsEnabled(enabled: boolean):void {} + + setAccessibilityFeatures(accessibilityFeatureFlags: number, responseId: number): void { + if (this.isAttached()) { + this.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); + } + } + + nativeSetAccessibilityFeatures(accessibilityFeatureFlags: number, responseId: number): void {} + + 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); + } + } + + nativeDispatchSemanticsAction(virtualViewId: number, action: Action, responseId: number): void {} + + 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); + } + } + + setLocalizationPlugin(localizationPlugin: LocalizationPlugin): void { + this.localizationPlugin = localizationPlugin; + } + + /** + * 获取系统语言列表 + * @param rate + */ + 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]); + } + flutter.nativeGetSystemLanguages(this.nativeShellHolderId, systemLanguages); + } +} + +export interface AccessibilityDelegate { + updateCustomAccessibilityActions(buffer: ByteBuffer, strings: string[]): void; + + updateSemantics(buffer: ByteBuffer, strings: string[], stringAttributeArgs: ByteBuffer[]): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterShellArgs.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterShellArgs.ts new file mode 100644 index 0000000000..7a3cd75a49 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/FlutterShellArgs.ts @@ -0,0 +1,86 @@ +/* +* 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 Want from '@ohos.app.ability.Want'; + +/** + * 封装flutter shell的参数 + */ +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"; + + /** + * 从意图中解析参数,创建shellArgs + * @returns + */ + static fromWant(want: Want): FlutterShellArgs { + //tdo 解析want + return new FlutterShellArgs(); + } + + //参数 + args: Set = new Set(); + + add(arg: string) { + this.args.add(arg); + } + + remove(arg: string) { + this.args.delete(arg); + } + + toArray(): Array { + return Array.from(this.args); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartExecutor.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartExecutor.ts new file mode 100644 index 0000000000..4b952c42f8 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartExecutor.ts @@ -0,0 +1,380 @@ +/* +* 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 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'; + + +const TAG = "DartExecutor"; + +/** + * dart代码执行器 + */ +export default class DartExecutor extends BinaryMessenger { + flutterNapi: FlutterNapi; + assetManager: resourceManager.ResourceManager; + private dartMessenger: DartMessenger; + private binaryMessenger: BinaryMessenger; + private isApplicationRunning: boolean; + private isolateServiceId: String = null; + private isolateServiceIdListener: IsolateServiceIdListener = null; + + private isolateChannelMessageHandler: BinaryMessageHandler = + new IsolateChannelMessageHandler(this.isolateServiceId, this.isolateServiceIdListener); + + constructor(flutterNapi: FlutterNapi, assetManager: resourceManager.ResourceManager) { + super(); + 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 JNI might already be attached if coming from a spawned engine. If so, correctly report + // that this DartExecutor is already running. + if (flutterNapi.isAttached()) { + this.isApplicationRunning = true; + } + } + + + /** + * Invoked when the {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link + * DartExecutor} attaches to JNI. + * + *

When attached to JNI, this {@link DartExecutor} begins handling 2-way communication to/from + * the Dart execution context. This communication is facilitate via 2 APIs: + * + *

    + *
  • {@link BinaryMessenger}, which sends messages to Dart + *
  • {@link 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 {@link io.flutter.embedding.engine.FlutterEngine} that owns this {@link + * DartExecutor} detaches from JNI. + * + *

When detached from JNI, this {@link 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); + } + + /** + * Is this {@link DartExecutor} currently executing Dart code? + * + * @return true if Dart code is being executed, false otherwise + */ + isExecutingDart(): boolean { + return this.isApplicationRunning; + } + + /** + * Starts executing Dart code based on the given {@code dartEntrypoint} and the {@code + * dartEntrypointArgs}. + * + *

See {@link 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 string 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; + } + + 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.end("DartExecutor#executeDartEntrypoint"); + } + } + + /** + * Starts executing Dart code based on the given {@code dartCallback}. + * + *

See {@link 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; + } + + 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, + null); + + this.isApplicationRunning = true; + } finally { + TraceSection.end("DartExecutor#executeDartCallback"); + } + } + + /** + * Returns a {@link BinaryMessenger} that can be used to send messages to, and receive messages + * from, Dart code that this {@code DartExecutor} is executing. + */ + + getBinaryMessenger(): BinaryMessenger { + return this.binaryMessenger; + } + + makeBackgroundTaskQueue(options: TaskQueueOptions): TaskQueue { + return this.getBinaryMessenger().makeBackgroundTaskQueue(options); + } + + + send(channel: String, message: ArrayBuffer, callback?: BinaryReply): void { + this.getBinaryMessenger().send(channel, message, callback); + } + + setMessageHandler(channel: String, handler: BinaryMessageHandler, taskQueue?: TaskQueue): void { + this.getBinaryMessenger().setMessageHandler(channel, handler, taskQueue); + } + + enableBufferingIncomingMessages(): void { + this.getBinaryMessenger().enableBufferingIncomingMessages(); + } + + + /** + * Returns the number of pending channel callback replies. + * + *

When sending messages to the Flutter application using {@link BinaryMessenger#send(String, + * ByteBuffer, io.flutter.plugin.common.BinaryMessenger.BinaryReply)}, 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. + * + *

Currently, it's mainly useful for a testing framework like Espresso to determine whether all + * the async channel callbacks are handled and the app is idle. + */ + getPendingChannelResponseCount(): number { + return this.dartMessenger.getPendingChannelResponseCount(); + } + + /** + * Returns an identifier for this executor's primary isolate. This identifier can be used in + * queries to the Dart service protocol. + */ + + getIsolateServiceId(): String { + return this.isolateServiceId; + } + + + + /** + * Set a listener that will be notified when an isolate identifier is available for this + * executor's primary isolate. + */ + setIsolateServiceIdListener(listener: IsolateServiceIdListener): void { + this.isolateServiceIdListener = listener; + if (this.isolateServiceIdListener != null && this.isolateServiceId != null) { + this.isolateServiceIdListener.onIsolateServiceIdAvailable(this.isolateServiceId); + } + } + + /** + * Notify the Dart VM of a low memory event, or that the application is in a state such that now + * is an appropriate time to free resources, such as going to the background. + * + *

This does not notify a Flutter application about memory pressure. For that, use the {@link + * io.flutter.embedding.engine.systemchannels.SystemChannel#sendMemoryPressureWarning}. + * + *

Calling this method may cause jank or latency in the application. Avoid calling it during + * critical periods like application startup or periods of animation. + */ + notifyLowMemoryWarning(): void { + if (this.flutterNapi.isAttached()) { + this.flutterNapi.notifyLowMemoryWarning(); + } + } + + disableBufferingIncomingMessages(): void { + this.getBinaryMessenger().enableBufferingIncomingMessages(); + } +} + + +/** + * 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 AssetManager 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; + + constructor(pathToBundle: string, + dartEntrypointLibrary: string, + dartEntrypointFunctionName: string) { + this.pathToBundle = pathToBundle; + this.dartEntrypointLibrary = dartEntrypointLibrary; + this.dartEntrypointFunctionName = dartEntrypointFunctionName; + } + + 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(), null, "main"); + } +} + + +/** Callback interface invoked when the isolate identifier becomes available. */ +interface IsolateServiceIdListener { + 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 Android AssetManager, provided from some {@code Context} or {@code Resources}. */ + public resourceManager: resourceManager.ResourceManager; + + /** The path within the AssetManager where the app will look for assets. */ + public pathToBundle: string; + + /** A Dart callback that was previously registered with the Dart VM. */ + public callbackHandle: FlutterCallbackInformation; + + constructor(resourceManager: resourceManager.ResourceManager, + pathToBundle: string, + callbackHandle: FlutterCallbackInformation) { + this.resourceManager = resourceManager; + this.pathToBundle = pathToBundle; + this.callbackHandle = callbackHandle; + } + + toString(): String { + return "DartCallback( bundle path: " + + this.pathToBundle + + ", library path: " + + this.callbackHandle.callbackLibraryPath + + ", function: " + + this.callbackHandle.callbackName + + " )"; + } +} + +export class DefaultBinaryMessenger implements BinaryMessenger { + private messenger: DartMessenger; + + constructor(messenger: DartMessenger) { + this.messenger = messenger; + } + + makeBackgroundTaskQueue(options: TaskQueueOptions): TaskQueue { + return this.messenger.makeBackgroundTaskQueue(options); + } + + /** + * Sends the given {@code messages} from Android to Dart over the given {@code channel} and then + * has the provided {@code callback} invoked when the Dart side responds. + * + * @param channel the name of the logical channel used for the message. + * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message + * bytes between position zero and current position, or null. + * @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 {@link io.flutter.plugin.common.BinaryMessenger.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 {@link BinaryMessageHandler} to be invoked on incoming messages, or null. + */ + setMessageHandler(channel: String, handler: BinaryMessageHandler, taskQueue?: TaskQueue): void { + this.messenger.setMessageHandler(channel, handler); + } + + enableBufferingIncomingMessages(): void { + this.messenger.enableBufferingIncomingMessages(); + } + + disableBufferingIncomingMessages(): void { + this.messenger.disableBufferingIncomingMessages(); + } +} + +class IsolateChannelMessageHandler implements BinaryMessageHandler { + private isolateServiceId: String; + private isolateServiceIdListener: IsolateServiceIdListener; + + constructor(isolateServiceId: String, isolateServiceIdListener: IsolateServiceIdListener) { + this.isolateServiceId = isolateServiceId; + this.isolateServiceIdListener = isolateServiceIdListener; + } + + 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ts new file mode 100644 index 0000000000..0a2c32013c --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/DartMessenger.ts @@ -0,0 +1,235 @@ +/* +* 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 Log from '../../../util/Log' +import { BinaryMessageHandler, BinaryMessenger, BinaryReply, TaskQueue, TaskQueueOptions } from '../../../plugin/common/BinaryMessenger'; +import FlutterNapi from '../FlutterNapi'; +import { PlatformMessageHandler } from './PlatformMessageHandler'; +import { TraceSection } from '../../../util/TraceSection'; + +/** + * Message conduit for 2-way communication between Android and Dart. + * + *

See {@link BinaryMessenger}, which sends messages from Android to Dart + * + *

See {@link PlatformMessageHandler}, which handles messages to Android from Dart + */ + +const TAG = "DartMessenger"; + +export class DartMessenger implements BinaryMessenger, PlatformMessageHandler { + + flutterNapi: FlutterNapi; + + /** + * Maps a channel name to an object that contains the task queue and the handler associated with + * the channel. + * + *

Reads and writes to this map must lock {@code handlersLock}. + */ + messageHandlers: Map = new Map(); + + /** + * Maps a channel name to an object that holds information about the incoming Dart message. + * + *

Reads and writes to this map must lock {@code handlersLock}. + */ + bufferedMessages: Map = new Map(); + + handlersLock: Object = new Object(); + enableBufferingIncomingMessagesFlag: boolean = false; + + pendingReplies: Map = new Map(); + nextReplyId: number = 1; + + constructor(flutterNapi: FlutterNapi) { + this.flutterNapi = flutterNapi; + } + makeBackgroundTaskQueue(options?: TaskQueueOptions): TaskQueue { + throw new Error('Method not implemented.'); + } + + + setMessageHandler(channel: String, handler: BinaryMessageHandler): void { + if (handler == null) { + Log.d(TAG, "Removing handler for channel '" + channel + "'"); + this.messageHandlers.delete(channel); + return; + } + Log.d(TAG, "Setting handler for channel '" + channel + "'"); + + this.messageHandlers.set(channel, new HandlerInfo(handler)); + this.bufferedMessages.delete(channel); + } + + + enableBufferingIncomingMessages(): void { + this.enableBufferingIncomingMessagesFlag = true; + } + + disableBufferingIncomingMessages(): void { + this.enableBufferingIncomingMessagesFlag = false; + this.bufferedMessages = new Map(); + } + + send(channel: String, message: ArrayBuffer, callback?: BinaryReply): void { + Log.d(TAG, "Sending message over channel '" + channel + "'"); + 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.end("DartMessenger#send on " + channel); + } + } + + invokeHandler(handlerInfo: HandlerInfo, message: ArrayBuffer, replyId: number): void { + // Called from any thread. + if (handlerInfo != null) { + try { + Log.d(TAG, "Deferring to registered handler to process message."); + handlerInfo.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); + } + } + + handleMessageFromDart(channel: String, message: ArrayBuffer, replyId: number, messageData: number): void { + // Called from any thread. + Log.d(TAG, "Received message from Dart over channel '" + channel + "'"); + + let handlerInfo: HandlerInfo; + let messageDeferred: boolean; + handlerInfo = this.messageHandlers.get(channel); + messageDeferred = (this.enableBufferingIncomingMessagesFlag && handlerInfo == null); + if (messageDeferred) { + if (!this.bufferedMessages.has(channel)) { + this.bufferedMessages.set(channel, new BufferedMessageInfo[0]); + } + let buffer: BufferedMessageInfo[] = this.bufferedMessages.get(channel); + buffer.push(new BufferedMessageInfo(message, replyId, messageData)); + } + if (!messageDeferred) { + this.invokeHandler(handlerInfo, message, replyId); + } + } + + handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void { + Log.d(TAG, "Received message reply from Dart."); + let callback: BinaryReply = this.pendingReplies.get(replyId); + 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 {@link BinaryMessenger#send(String, + * ByteBuffer, io.flutter.plugin.common.BinaryMessenger.BinaryReply)}, 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. + */ + getPendingChannelResponseCount(): number { + return this.pendingReplies.size; + } +} + + + + + + +/** + * Holds information about a platform handler, such as the task queue that processes messages from + * Dart. + */ +class HandlerInfo { + handler: BinaryMessageHandler; + + constructor(handler: BinaryMessageHandler) { + this.handler = handler; + } +} + +/** + * Holds information that allows to dispatch a Dart message to a platform handler when it becomes + * available. + */ +class BufferedMessageInfo { + message: ArrayBuffer; + replyId: number; + messageData: number; + + constructor(message: ArrayBuffer, + replyId: number, + messageData: number) { + this.message = message; + this.replyId = replyId; + this.messageData = messageData; + } +} + + + + + +class Reply implements BinaryReply { + flutterNapi: FlutterNapi; + replyId: number; + done: boolean = false; + + constructor(flutterNapi: FlutterNapi, replyId: number) { + this.flutterNapi = flutterNapi; + this.replyId = replyId; + } + + reply(reply: ArrayBuffer) { + 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); + } + } +} + +interface DartMessengerTaskQueue { + dispatch(): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ts new file mode 100644 index 0000000000..b6e3411862 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/dart/PlatformMessageHandler.ts @@ -0,0 +1,22 @@ +/* +* 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. +*/ + +export interface PlatformMessageHandler { + + handleMessageFromDart(channel: String,message: ArrayBuffer,replyId: number, messageData: number): void; + + handlePlatformMessageResponse(replyId: number, reply: ArrayBuffer): void; + +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ts new file mode 100644 index 0000000000..b8af4f6fb7 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/ApplicationInfoLoader.ts @@ -0,0 +1,24 @@ +/* +* 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 FlutterApplicationInfo from './FlutterApplicationInfo'; +import common from '@ohos.app.ability.common'; + +export default class ApplicationInfoLoader { + static load(context: common.Context) { + let applicatioinInfo = new FlutterApplicationInfo(null, null, null, null, null, context.bundleCodeDir + '/libs/arm64', true); + return applicatioinInfo + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ts new file mode 100644 index 0000000000..0db68ee9da --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterApplicationInfo.ts @@ -0,0 +1,50 @@ +/* +* 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. +*/ + +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"; + + +/** + * application 信息,后期看如何设置 + */ +export default class FlutterApplicationInfo { + aotSharedLibraryName: string; + vmSnapshotData: string; + isolateSnapshotData: string; + flutterAssetsDir: string; + domainNetworkPolicy: string; + nativeLibraryDir: string; + automaticallyRegisterPlugins: boolean; + //是否是开发模式,先放在这里,后续应该从context获取 + isDebugMode: boolean; + //是否是profile模式 + isProfile: boolean; + + constructor(aotSharedLibraryName: string, vmSnapshotData: string, isolateSnapshotData: string, flutterAssetsDir: string, domainNetworkPolicy: string, + 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 = true; + this.isProfile = false; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterLoader.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterLoader.ts new file mode 100644 index 0000000000..730da648d2 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/loader/FlutterLoader.ts @@ -0,0 +1,221 @@ +/* +* 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. +*/ + +/** + * flutterLoader,负责dart虚拟机启动和dart代码加载 + */ +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'; + +const TAG = "FlutterLoader"; + +//flutter引擎so +const DEFAULT_LIBRARY = "libflutter.so"; +//jit产物默认kenel文件 +const DEFAULT_KERNEL_BLOB = "kernel_blob.bin"; +//jit产物,默认快照文件 +const VMSERVICE_SNAPSHOT_LIBRARY = "libvmservice_snapshot.so"; +//key值 +const SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path"; +//key值 +const VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data"; +//key值 +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"; + +//文件路径分隔符 +const FILE_SEPARATOR = "/"; + +/** + * 定位在hap包中的flutter资源,并且加载flutter native library. + */ +export default class FlutterLoader { + flutterNapi: FlutterNapi; + initResult: InitResult; + flutterApplicationInfo: FlutterApplicationInfo; + context: common.Context; + initialized: boolean; + //初始化开始时间戳 + initStartTimestampMillis: number; + + constructor(flutterNapi: FlutterNapi) { + this.flutterNapi = flutterNapi; + } + + /** + * Starts initialization of the native system. + * + *

This loads the Flutter engine's native library to enable subsequent JNI calls. This also + * starts locating and unpacking Dart resources packaged in the app's APK. + * + *

Calling this method multiple times has no effect. + * + * @param applicationContext The Android application context. + * @param settings Configuration settings. + */ + async startInitialization(context: common.Context) { + Log.d(TAG, "flutterLoader start init") + this.initStartTimestampMillis = Date.now(); + this.context = context; + this.flutterApplicationInfo = ApplicationInfoLoader.load(context); + Log.d(TAG, "context.filesDir=" + context.filesDir) + Log.d(TAG, "context.cacheDir=" + context.cacheDir) + Log.d(TAG, "context.bundleCodeDir=" + context.bundleCodeDir) + if (this.flutterApplicationInfo.isDebugMode) { + await this.copyResource(context) + } + this.initResult = new InitResult( + `${context.filesDir}/`, + `${context.cacheDir}/`, + `${context.filesDir}` + ) + Log.d(TAG, "flutterLoader end init") + } + + private async copyResource(context: common.Context) { + let filePath = context.filesDir + FILE_SEPARATOR + this.flutterApplicationInfo.flutterAssetsDir + if (!fs.accessSync(filePath + FILE_SEPARATOR + DEFAULT_KERNEL_BLOB)) { + Log.d(TAG, "start copyResource") + fs.mkdirSync(filePath) + + let icudtlBuffer = await this.context.resourceManager.getRawFileContent(this.flutterApplicationInfo.flutterAssetsDir + "/icudtl.dat") + let icudtlFile = fs.openSync(filePath + FILE_SEPARATOR + "/icudtl.dat", fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE) + fs.writeSync(icudtlFile.fd, icudtlBuffer.buffer) + + let kernelBuffer = await this.context.resourceManager.getRawFileContent(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 = await this.context.resourceManager.getRawFileContent(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 = await this.context.resourceManager.getRawFileContent(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) + Log.d(TAG, "copyResource end") + } else { + Log.d(TAG, "no copyResource") + } + } + + /** + * 初始化dart虚拟机方法 + * @param flutterShellArgs + */ + ensureInitializationComplete(shellArgs: Array) { + if (this.initialized) { + return; + } + Log.d(TAG, "ensureInitializationComplete") + 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("--icu-data-file-path=" + snapshotAssetPath + "/icudtl.dat") + 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); + } else { + shellArgs.push( + "--" + AOT_SHARED_LIBRARY_NAME + "=" + this.flutterApplicationInfo.aotSharedLibraryName); + shellArgs.push( + "--" + + AOT_SHARED_LIBRARY_NAME + + "=" + + this.flutterApplicationInfo.nativeLibraryDir + + FILE_SEPARATOR + + this.flutterApplicationInfo.aotSharedLibraryName); + 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); + } + + //todo 是否有其他需要迁移得参数 + 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); + + shellArgs.push("--enable-impeller"); + + // //最终初始化操作 + const costTime = Date.now() - this.initStartTimestampMillis; + this.flutterNapi.init( + this.context, + shellArgs, + kernelPath, + this.initResult.appStoragePath, + this.initResult.engineCachesPath, + costTime + ); + this.initialized = true; + } + + findAppBundlePath(): string { + return this.flutterApplicationInfo.flutterAssetsDir; + } + + getLookupKeyForAsset(asset: string, packageName?: string): string { + return this.fullAssetPathFrom(asset); + } + + fullAssetPathFrom(filePath: string): string { + return this.flutterApplicationInfo.flutterAssetsDir + "/" + filePath; + } +} + +class InitResult { + appStoragePath: string; + engineCachesPath: string; + dataDirPath: string; + + constructor(appStoragePath: string, + engineCachesPath: string, + dataDirPath: string) { + this.appStoragePath = appStoragePath; + this.engineCachesPath = engineCachesPath; + this.dataDirPath = dataDirPath; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/FlutterPlugin.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/FlutterPlugin.ts new file mode 100644 index 0000000000..7651dee5ee --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/FlutterPlugin.ts @@ -0,0 +1,111 @@ +/* +* 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 { BinaryMessenger } from '../../../plugin/common/BinaryMessenger'; +import FlutterEngineGroup from '../FlutterEngineGroup'; + +export interface FlutterPlugin { + //获取唯一的类名 类似安卓的ClassRelevant resources that this {@code FlutterPlugin} may need are provided via the {@code + * binding}. The {@code binding} may be cached and referenced until {@link + * #onDetachedFromEngine(FlutterPluginBinding)} is invoked and returns. + */ + onAttachedToEngine(binding: FlutterPluginBinding): void; + + /** + * This {@code FlutterPlugin} has been removed from a {@link + * io.flutter.embedding.engine.FlutterEngine} instance. + * + *

The {@code binding} passed to this method is the same instance that was passed in {@link + * #onAttachedToEngine(FlutterPluginBinding)}. It is provided again in this method as a + * convenience. The {@code binding} may be referenced during the execution of this method, but it + * must not be cached or referenced after this method returns. + * + *

{@code FlutterPlugin}s should release all resources in this method. + */ + onDetachedFromEngine(binding: FlutterPluginBinding): void; +} + +export class FlutterPluginBinding { + private applicationContext: common.Context; + private binaryMessenger: BinaryMessenger; + private flutterAssets: FlutterAssets; + private group: FlutterEngineGroup; + + constructor(applicationContext: common.Context, binaryMessenger: BinaryMessenger, flutterAssets: FlutterAssets, group: FlutterEngineGroup) { + this.applicationContext = applicationContext; + this.binaryMessenger = binaryMessenger; + this.flutterAssets = flutterAssets; + this.group = group; + } + + getApplicationContext(): common.Context { + return this.applicationContext; + } + + getBinaryMessenger(): BinaryMessenger { + return this.binaryMessenger; + } + + getFlutterAssets(): FlutterAssets { + return this.flutterAssets; + } + + getEngineGroup(): FlutterEngineGroup { + return this.group; + } +} + +/** 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., {@code "myImage.jpg"}. + * + *

The returned file path is relative to the Ohos app's standard assets directory. + * Therefore, the returned path is appropriate to pass to Ohos's {@code ResourceManage}, but + * the path is not appropriate to load as an absolute path. + */ + getAssetFilePathByName(assetFileName: string): string; + + /** + * Same as {@link #getAssetFilePathByName(String)} but with added support for an explicit + * Ohos {@code bundleName}. + */ + 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., {@code "/dir1/dir2/myImage.jpg"}. + * + *

The returned file path is relative to the Ohos app's standard assets directory. + * Therefore, the returned path is appropriate to pass to Ohos's {@code ResourceManage}, but + * the path is not appropriate to load as an absolute path. + */ + getAssetFilePathBySubpath(assetSubpath: string): string; + + /** + * Same as {@link #getAssetFilePathBySubpath(String)} but with added support for an explicit + * Ohos {@code bundleName}. + */ + getAssetFilePathBySubpath(assetSubpath: string, bundleName: string): string; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/PluginRegistry.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/PluginRegistry.ts new file mode 100644 index 0000000000..1a322ec2ba --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/PluginRegistry.ts @@ -0,0 +1,73 @@ +/* +* 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 { FlutterPlugin } from './FlutterPlugin'; + +export default interface PluginRegistry { + /** + * Attaches the given {@code plugin} to the {@link io.flutter.embedding.engine.FlutterEngine} + * associated with this {@code PluginRegistry}. + */ + add(plugin: FlutterPlugin): void; + + /** + * Attaches the given {@code plugins} to the {@link io.flutter.embedding.engine.FlutterEngine} + * associated with this {@code PluginRegistry}. + */ + addList(plugins: Set): void; + + /** + * Returns true if a plugin of the given type is currently attached to the {@link + * io.flutter.embedding.engine.FlutterEngine} associated with this {@code PluginRegistry}. + */ + //Class + has(pluginClassName: string): boolean; + + /** + * Returns the instance of a plugin that is currently attached to the {@link + * io.flutter.embedding.engine.FlutterEngine} associated with this {@code PluginRegistry}, which + * matches the given {@code pluginClass}. + * + *

If no matching plugin is found, {@code null} is returned. + */ + //Class + get(pluginClassName: string): FlutterPlugin; + + /** + * Detaches the plugin of the given type from the {@link + * io.flutter.embedding.engine.FlutterEngine} associated with this {@code PluginRegistry}. + * + *

If no such plugin exists, this method does nothing. + */ + //Class + remove(pluginClassName: string): void; + + /** + * Detaches the plugins of the given types from the {@link + * io.flutter.embedding.engine.FlutterEngine} associated with this {@code PluginRegistry}. + * + *

If no such plugins exist, this method does nothing. + */ + //Class + removeList(pluginClassNames: Set): void; + + /** + * Detaches all plugins that are currently attached to the {@link + * io.flutter.embedding.engine.FlutterEngine} associated with this {@code PluginRegistry}. + * + *

If no plugins are currently attached, this method does nothing. + */ + removeAll(): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ts new file mode 100644 index 0000000000..bd68f68aee --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityAware.ts @@ -0,0 +1,75 @@ +/* +* 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. +*/ + +/** + * {@link io.flutter.embedding.engine.plugins.FlutterPlugin} that is interested in {@link + * ohos.app.ability.UIAbility} lifecycle events related to a {@link + * io.flutter.embedding.engine.FlutterEngine} running within the given {@link ohos.app.ability.UIAbility}. + */ +import { AbilityPluginBinding } from './AbilityPluginBinding'; + +export default interface AbilityAware { + /** + * This {@code AbilityAware} {@link io.flutter.embedding.engine.plugins.FlutterPlugin} is now + * associated with an {@link ohos.app.ability.UIAbility}. + * + *

This method can be invoked in 1 of 2 situations: + * + *

    + *
  • This {@code AbilityAware} {@link io.flutter.embedding.engine.plugins.FlutterPlugin} was + * just added to a {@link io.flutter.embedding.engine.FlutterEngine} that was already + * connected to a running {@link ohos.app.ability.UIAbility}. + *
  • This {@code AbilityAware} {@link io.flutter.embedding.engine.plugins.FlutterPlugin} was + * already added to a {@link io.flutter.embedding.engine.FlutterEngine} and that {@link + * io.flutter.embedding.engine.FlutterEngine} was just connected to an {@link + * ohos.app.ability.UIAbility}. + *
+ * + * The given {@link AbilityPluginBinding} contains {@link ohos.app.ability.UIAbility}-related + * references that an {@code AbilityAware} {@link + * io.flutter.embedding.engine.plugins.FlutterPlugin} may require, such as a reference to the + * actual {@link ohos.app.ability.UIAbility} in question. The {@link AbilityPluginBinding} may be + * referenced until either {@link #onDetachedFromAbilityForConfigChanges()} or {@link + * #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. + */ + onAttachedToAbility(binding: AbilityPluginBinding): void ; + + /** + * This plugin has been detached from an {@link ohos.app.ability.UIAbility}. + * + *

Detachment can occur for a number of reasons. + * + *

    + *
  • The app is no longer visible and the {@link ohos.app.ability.UIAbility} instance has been + * destroyed. + *
  • The {@link io.flutter.embedding.engine.FlutterEngine} that this plugin is connected to + * has been detached from its {@link io.flutter.embedding.android.FlutterView}. + *
  • This {@code AbilityAware} plugin has been removed from its {@link + * io.flutter.embedding.engine.FlutterEngine}. + *
+ * + * By the end of this method, the {@link ohos.app.ability.UIAbility} that was made available in {@link + * #onAttachedToAbility(AbilityPluginBinding)} is no longer valid. Any references to the + * associated {@link ohos.app.ability.UIAbility} or {@link AbilityPluginBinding} should be cleared. + * + *

Any {@code Lifecycle} listeners that were registered in {@link + * #onAttachedToAbility(AbilityPluginBinding)} or {@link + * #onReattachedToAbilityForConfigChanges(AbilityPluginBinding)} should be deregistered here to + * avoid a possible memory leak and other side effects. + */ + onDetachedFromAbility(): void; +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ts new file mode 100644 index 0000000000..b93b7b14b5 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityControlSurface.ts @@ -0,0 +1,27 @@ +/* +* 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 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'; + +export default interface ActivityControlSurface { + attachToAbility(exclusiveActivity: ExclusiveAppComponent): void; + detachFromAbility(): void; + onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; + onWindowFocusChanged(hasFocus: boolean): void; + onSaveState(reason: AbilityConstant.StateType, wantParam: { [key: string]: Object; }): AbilityConstant.OnSaveResult; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ts new file mode 100644 index 0000000000..613b69e0f4 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding.ts @@ -0,0 +1,84 @@ +/* +* 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 UIAbility from '@ohos.app.ability.UIAbility' +import Want from '@ohos.app.ability.Want'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + +export interface AbilityPluginBinding { + getAbility(): UIAbility; + /** + * Adds a listener that is invoked whenever the associated {@link ohos.app.ability.UIAbility}'s {@code + * onNewWant(...)} method is invoked. + */ + addOnNewWantListener(listener: NewWantListener): void; + + /** + * Removes a listener that was added in {@link + * #addOnNewWantListener(NewWantListener)}. + */ + removeOnNewWantListener(listener: NewWantListener): void; + + /** + * Adds a listener that is invoked whenever the associated {@link ohos.app.ability.UIAbility}'s {@code + * windowStageEvent} method is invoked. + */ + addOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void; + + /** + * Removes a listener that was added in {@link + * #addOnWindowFocusChangedListener(WindowFocusChangedListener)}. + */ + removeOnWindowFocusChangedListener(listener: WindowFocusChangedListener): void; + + /** + * Adds a listener that is invoked when the associated {@code UIAbility} saves + * and restores instance state. + */ + addOnSaveStateListener(listener: OnSaveStateListener): void; + + /** + * Removes a listener that was added in {@link + * #addOnSaveStateListener(OnSaveStateListener)}. + */ + removeOnSaveStateListener(listener: OnSaveStateListener): void; +} + +/** + * Delegate interface for handling new wants on behalf of the main {@link ohos.app.ability.UIAbility}. + */ +export interface NewWantListener { + /** + * @param intent The new want that was started for the UIAbility. + * @return true if the new want has been handled. + */ + onNewWant(want: Want, launchParams: AbilityConstant.LaunchParam): void; +} + +/** + * Delegate interface for handling window focus changes on behalf of the main {@link + * ohos.app.ability.UIAbility}. + */ +export interface WindowFocusChangedListener { + onWindowFocusChanged(hasFocus): void; +} + +export interface OnSaveStateListener { + /** + * Invoked when the associated {@code UIAbility} or {@code Fragment} executes {@link + * Activity#onSaveState(Bundle)}. + */ + onSaveState(reason: AbilityConstant.StateType, wantParam: { [key: string]: Object; }): AbilityConstant.OnSaveResult; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ts new file mode 100644 index 0000000000..6c930e1e74 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/AccessibilityChannel.ts @@ -0,0 +1,119 @@ +/* +* 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 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 { Action } from '../../../view/AccessibilityBridge' +import StandardMessageCodec from '../../../plugin/common/StandardMessageCodec'; +import { MethodCallHandler } from '../../../plugin/common/MethodChannel'; + +/** +* 辅助功能channel +*/ +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; + + onMessage(message: object, reply: Reply): void { + if (this.handler == null) { + Log.i(AccessibilityChannel.TAG, "NULL"); + reply.reply(null); + 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) { + 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(null); + } + + 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; + } + + onOhosAccessibilityEnabled(): void { + let replyId: number = this.nextReplyId++; + this.flutterNapi.setSemanticsEnabled(true, replyId); + } + + onOhosAccessibilityFeatures(accessibilityFeatureFlags: number): void { + let replyId: number = this.nextReplyId++; + this.flutterNapi.setAccessibilityFeatures(accessibilityFeatureFlags, replyId); + } + + dispatchSemanticsAction(virtualViewId: number, action: Action): void { + let replyId: number = this.nextReplyId++; + this.flutterNapi.dispatchSemanticsAction(virtualViewId, action, replyId); + } + + setAccessibilityMessageHandler(handler: AccessibilityMessageHandler): void { + this.handler = handler; + let replyId: number = this.nextReplyId++; + this.flutterNapi.setAccessibilityDelegate(handler, replyId); + } + +} + +interface AccessibilityMessageHandler extends AccessibilityDelegate { + announce(message: string): void; + onTap(nodeId: number): void; + onLongPress(nodeId: number): void; + onTooltip(nodeId: string): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ts new file mode 100644 index 0000000000..d397f363bc --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/KeyEventChannel.ts @@ -0,0 +1,75 @@ +/* +* 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 BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; +import { BinaryMessenger } from '../../../plugin/common/BinaryMessenger'; +import { Reply } from '../../../plugin/common/BasicMessageChannel'; +import { Action, Key, KeyEvent } from '@ohos.multimodalInput.keyEvent'; +import Log from '../../../util/Log'; +import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; + +export default class KeyEventChannel { + private static TAG = "KeyEventChannel"; + private static CHANNEL_NAME = "flutter/keyevent"; + private channel : BasicMessageChannel>; + + constructor(binaryMessenger: BinaryMessenger) { + this.channel = new BasicMessageChannel(binaryMessenger, KeyEventChannel.CHANNEL_NAME, JSONMessageCodec.INSTANCE); + } + + sendFlutterKeyEvent(keyEvent: FlutterKeyEvent, + isKeyUp: boolean, + responseHandler: EventResponseHandler): void { + this.channel.send(this.encodeKeyEvent(keyEvent, isKeyUp), + (message:Map) => { + let isEventHandled = false; + try { + if (message != null) { + isEventHandled = message.get("handled") as boolean; + } + } catch (e) { + Log.e(KeyEventChannel.TAG, "Unable to unpack JSON message: " + e); + } + responseHandler.onFrameworkResponse(isEventHandled); + } + ); + } + + encodeKeyEvent(keyEvent: FlutterKeyEvent, isKeyUp: boolean): Map { + let message: Map = new Map(); + message.set("type", isKeyUp ? "keyup" : "keydown"); + message.set("keymap", "ohos"); + message.set("codePoint", keyEvent.event.unicodeChar); + message.set("keyCode", keyEvent.event.key.code); + message.set("deviceId", keyEvent.event.key.deviceId); + message.set("character", keyEvent.event.key.code.toString()); + message.set("repeatCount", 1); + return message; + } + + +} + +export interface EventResponseHandler { + onFrameworkResponse(isEventHandled: boolean): void; +} + +export class FlutterKeyEvent { + event: KeyEvent; + + constructor( ohosKeyEvent: KeyEvent) { + this.event = ohosKeyEvent; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ts new file mode 100644 index 0000000000..ce5acbdbba --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LifecycleChannel.ts @@ -0,0 +1,105 @@ +/* +* 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 Log from '../../../util/Log'; +import StringCodec from '../../../plugin/common/StringCodec'; +import DartExecutor from '../dart/DartExecutor'; +import BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; + +/** + * 生命周期channel + */ +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 + + constructor(dartExecutor: DartExecutor) { + this.channel = new BasicMessageChannel(dartExecutor, LifecycleChannel.CHANNEL_NAME, StringCodec.INSTANCE) + } + + // Called if at least one window in the app has focus. + aWindowIsFocused(): void { + this.sendState(this.lastOhosState, true); + } + + // Called if no windows in the app have focus. + noWindowsAreFocused(): void { + this.sendState(this.lastOhosState, false); + } + + appIsResumed(): void { + this.sendState(LifecycleChannel.RESUMED, this.lastFocus); + } + + appIsInactive(): void { + this.sendState(LifecycleChannel.INACTIVE, this.lastFocus); + } + + appIsPaused(): void { + this.sendState(LifecycleChannel.PAUSED, this.lastFocus); + } + + 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ts new file mode 100644 index 0000000000..c42816e951 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/LocalizationChannel.ts @@ -0,0 +1,70 @@ +/* +* 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 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"; +export default class LocalizationChannel implements MethodCallHandler{ + private static TAG = "LocalizationChannel"; + private static CHANNEL_NAME = "flutter/localization"; + private channel: MethodChannel; + private localizationMessageHandler: LocalizationMessageHandler; + + 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": { + let key: string = call.argument("key"); + let localeString: string = null; + if (call.hasArgument("locale")) { + localeString = call.argument("locale"); + } + result.success(this.localizationMessageHandler.getStringResource(key, localeString)); + break; + } + default: { + result.notImplemented(); + break; + } + } + } + + constructor(dartExecutor: DartExecutor) { + this.channel = new MethodChannel(dartExecutor, LocalizationChannel.CHANNEL_NAME, JSONMethodCodec.INSTANCE); + this.channel.setMethodCallHandler(this); + } + + setLocalizationMessageHandler(localizationMessageHandler: LocalizationMessageHandler): void { + this.localizationMessageHandler = localizationMessageHandler; + } + + sendLocales(locales: Array): void { + this.channel.invokeMethod("setLocale", locales); + } +} + +export interface LocalizationMessageHandler { + getStringResource(key: string, local: string); +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ts new file mode 100644 index 0000000000..b6f1c89984 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/MouseCursorChannel.ts @@ -0,0 +1,85 @@ +/* +* 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 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' + +export default class MouseCursorChannel implements MethodCallHandler { + public channel: MethodChannel; + + private mouseCursorMethodHandler: MouseCursorMethodHandler; + + 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) + } + } + + constructor(dartExecutor: DartExecutor) { + this.channel = new MethodChannel(dartExecutor, "flutter/mousecursor", StandardMethodCodec.INSTANCE); + this.channel.setMethodCallHandler(this); + } + + /** + * Sets the {@link MouseCursorMethodHandler} which receives all events and requests that are + * parsed from the underlying platform channel. + * @param mouseCursorMethodHandler + */ + public setMethodHandler(mouseCursorMethodHandler: MouseCursorMethodHandler): void { + this.mouseCursorMethodHandler = mouseCursorMethodHandler; + } + + public synthesizeMethodCall(call: MethodCall, result: MethodResult): void { + this.onMethodCall(call, result); + } +} + +export interface MouseCursorMethodHandler { + // Called when the pointer should start displaying a system mouse cursor + // specified by {@code shapeCode}. + activateSystemCursor(kind: String): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ts new file mode 100644 index 0000000000..6d591e818f --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/NavigationChannel.ts @@ -0,0 +1,62 @@ +/* +* 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 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'; + +export default class NavigationChannel { + private static TAG = "NavigationChannel"; + private channel: MethodChannel + + constructor(dartExecutor: DartExecutor) { + 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({ + onMethodCall(call: MethodCall, result: MethodResult): void { + result.success(null); + } + }); + } + + setInitialRoute(initialRoute: string): void { + Log.i(NavigationChannel.TAG, "Sending message to set initial route to '" + initialRoute + "'"); + this.channel.invokeMethod("setInitialRoute", initialRoute); + } + + pushRoute(route: string): void { + Log.i(NavigationChannel.TAG, "Sending message to push route '" + route + "'"); + this.channel.invokeMethod("pushRoute", route); + } + + pushRouteInformation(route: string): void { + Log.i(NavigationChannel.TAG, "Sending message to push route information '" + route + "'"); + this.channel.invokeMethod("pushRouteInformation", { + "location": route + }); + } + + popRoute(): void { + Log.i(NavigationChannel.TAG, "Sending message to pop route."); + this.channel.invokeMethod("popRoute", null); + } + + setMethodCallHandler(handler: MethodCallHandler) { + this.channel.setMethodCallHandler(handler); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ts new file mode 100644 index 0000000000..2f74564d5b --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/PlatformChannel.ts @@ -0,0 +1,485 @@ +/* +* 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 JSONMethodCodec from '../../../plugin/common/JSONMethodCodec'; +import MethodCall from '../../../plugin/common/MethodCall'; +import MethodChannel, { 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'; + +export default class PlatformChannel { + private static TAG = "PlatformChannel"; + private static CHANNEL_NAME = "flutter/platform"; + channel: MethodChannel; + platformMessageHandler: PlatformMessageHandler; + + constructor(dartExecutor: DartExecutor) { + this.channel = new MethodChannel(dartExecutor, PlatformChannel.CHANNEL_NAME, JSONMethodCodec.INSTANCE); + this.channel.setMethodCallHandler({ + onMethodCall: (call: MethodCall, result: MethodResult): void => { + if (this.platformMessageHandler == null) { + Log.w(PlatformChannel.TAG,"platformMessageHandler is null"); + return; + } + + let method: string = call.method; + let args: any = call.args; + Log.d(PlatformChannel.TAG, "Received '" + method + "' message."); + try { + switch (method) { + case "SystemSound.play": + //TODO: + break; + case "HapticFeedback.vibrate": + try { + Log.d(PlatformChannel.TAG, "HapticFeedback: "+args as string); + let feedbackType: HapticFeedbackType = this.getFeedbackTypeFromValue(args as string); + this.platformMessageHandler.vibrateHapticFeedback(feedbackType); + result.success(null); + } catch(e) { + Log.e(PlatformChannel.TAG, "HapticFeedback.vibrate error:" + JSON.stringify(e)); + } + break; + case "SystemChrome.setPreferredOrientations": + Log.d(PlatformChannel.TAG, "setPreferredOrientations: "+JSON.stringify(args)); + try { + let ohosOrientation = this.decodeOrientations(args as string[]); + this.platformMessageHandler.setPreferredOrientations(ohosOrientation); + result.success(null); + } catch (err) { + Log.e(PlatformChannel.TAG, "setPreferredOrientations err:" + JSON.stringify(err)); + result.error("error", JSON.stringify(err), null); + } + + break; + case "SystemChrome.setApplicationSwitcherDescription": + Log.d(PlatformChannel.TAG, "setApplicationSwitcherDescription: "+JSON.stringify(args)); + break; + case "SystemChrome.setEnabledSystemUIOverlays": + try { + let overlays: SystemUiOverlay[] = this.decodeSystemUiOverlays(args); + Log.d(PlatformChannel.TAG,"overlays: " + overlays); + this.platformMessageHandler.showSystemOverlays(overlays); + result.success(null); + } catch (err) { + Log.e(PlatformChannel.TAG, "setEnabledSystemUIOverlays err:" + JSON.stringify(err)); + result.error("error", JSON.stringify(err), null); + } + break; + case "SystemChrome.setEnabledSystemUIMode": + try{ + Log.d(PlatformChannel.TAG,"setEnabledSystemUIMode args:" + args as string); + let mode: SystemUiMode = this.decodeSystemUiMode(args as string) + this.platformMessageHandler.showSystemUiMode(mode); + } catch (err) { + Log.e(PlatformChannel.TAG, "setEnabledSystemUIMode err:" + JSON.stringify(err)); + result.error("error", JSON.stringify(err), null); + } + break; + case "SystemChrome.setSystemUIChangeListener": + this.platformMessageHandler.setSystemUiChangeListener(); + result.success(null); + break; + case "SystemChrome.restoreSystemUIOverlays": + this.platformMessageHandler.restoreSystemUiOverlays(); + result.success(null); + break; + case "SystemChrome.setSystemUIOverlayStyle": + try { + Log.d(PlatformChannel.TAG, "setSystemUIOverlayStyle asrgs: " + JSON.stringify(args)); + let systemChromeStyle: SystemChromeStyle = this.decodeSystemChromeStyle(args); + this.platformMessageHandler.setSystemUiOverlayStyle(systemChromeStyle); + result.success(null); + } catch (err) { + Log.e(PlatformChannel.TAG, "setSystemUIOverlayStyle err:" + JSON.stringify(err)); + result.error("error", JSON.stringify(err), null); + } + break; + case "SystemNavigator.pop": + this.platformMessageHandler.popSystemNavigator(); + result.success(null); + break; + case "Clipboard.getData": + let contentFormatName: string = args as string; + let clipboardFormat: ClipboardContentFormat = null; + if(contentFormatName != null) { + try{ + clipboardFormat = this.getClipboardContentFormatFromValue(contentFormatName); + } catch (err) { + Log.d(PlatformChannel.TAG, "No such clipboard content format: " + contentFormatName); + result.error("error", "No such clipboard content format: " + contentFormatName, null); + } + } + + let pasteBoard = pasteboard.getSystemPasteboard(); + pasteBoard.getData().then((pasteData) => { + let text = pasteData.getPrimaryText(); + let response = {text: ""}; + response.text = text; + result.success(response); + }).catch((err) => { + Log.e(PlatformChannel.TAG,"Failed to get PasteData. Cause: " + JSON.stringify(err)); + }); + break; + case "Clipboard.setData": + let clipboardContent = args.text; + this.platformMessageHandler.setClipboardData(clipboardContent); + result.success(null); + break; + case "Clipboard.hasStrings": + let hasStrings: boolean = false; + let response = {value: false}; + let systemPasteboard = pasteboard.getSystemPasteboard(); + systemPasteboard.hasData().then((hasData) => { + if(!hasData) { + response.value = hasData; + result.success(response); + } + }).catch((err) => { + Log.e(PlatformChannel.TAG, "systemPasteboard.hasData err: " + JSON.stringify(err)); + }) + systemPasteboard.getData().then((pasteData) => { + hasStrings = pasteData.hasType(pasteboard.MIMETYPE_TEXT_PLAIN); + response.value = hasStrings; + result.success(response); + }).catch((err) => { + Log.e(PlatformChannel.TAG, "getData err: " + JSON.stringify(err)); + }) + break; + default: + result.notImplemented(); + break; + } + } catch (e) { + result.error("error", JSON.stringify(e), null); + } + } + }); + } + + setPlatformMessageHandler(platformMessageHandler: PlatformMessageHandler): void { + this.platformMessageHandler = platformMessageHandler; + } + + systemChromeChanged(overlaysAreVisible: boolean): void { + Log.d(PlatformChannel.TAG, "Sending 'systemUIChange' message."); + this.channel.invokeMethod("SystemChrome.systemUIChange", [overlaysAreVisible]); + } + + 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 |= 0x02; + break; + case DeviceOrientation.LANDSCAPE_RIGHT: + requestedOrientation |= 0x08; + 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; + 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_INVERTED; + 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; + case 0x04: + return bundleManager.DisplayOrientation.PORTRAIT_INVERTED; + case 0x08: + return bundleManager.DisplayOrientation.LANDSCAPE_INVERTED; + } + case 0x0f: + } + return bundleManager.DisplayOrientation.PORTRAIT; + } + + getFeedbackTypeFromValue(encodedName: string): HapticFeedbackType { + if(encodedName == null) { + return HapticFeedbackType.STANDARD; + } + for (const feedbackType in HapticFeedbackType) { + if ( + (HapticFeedbackType[feedbackType] == encodedName) || + (HapticFeedbackType[feedbackType] == null && encodedName == null) + ) { + return HapticFeedbackType[feedbackType]; + } + } + Log.e(PlatformChannel.TAG,"No such HapticFeedbackType:" + encodedName); + } + + getClipboardContentFormatFromValue(encodedName: string): ClipboardContentFormat { + for (const format in ClipboardContentFormat) { + if (ClipboardContentFormat[format] === encodedName) { + return ClipboardContentFormat[format]; + } + } + } + getSystemUiOverlayFromValue(encodedName: string): SystemUiOverlay { + for (const overlay in SystemUiOverlay) { + if (SystemUiOverlay[overlay] === encodedName) { + return SystemUiOverlay[overlay]; + } + } + throw new Error("No such SystemUiOverlay: " + encodedName); + } + + private decodeSystemUiOverlays(encodedSystemUiOverlay: any): SystemUiOverlay[] { + let overlays: SystemUiOverlay[] = []; + for(let i = 0; i < encodedSystemUiOverlay.length; i++) { + const encodedOverlay = encodedSystemUiOverlay[i]; + const overlay = this.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; + } + + getSystemUiModeFromValue(encodedName: string): SystemUiMode { + for (const mode in SystemUiMode) { + if (SystemUiMode[mode] === encodedName) { + return SystemUiMode[mode]; + } + } + throw new Error("No such SystemUiOverlay: " + encodedName); + } + getBrightnessFromValue(encodedName: string): Brightness { + for (const brightness in Brightness) { + if (Brightness[brightness] === encodedName) { + return Brightness[brightness]; + } + } + throw new Error("No such Brightness: " + encodedName); + } + getDeviceOrientationFromValue(encodedName: string): DeviceOrientation { + for (const orientation in DeviceOrientation) { + if (DeviceOrientation[orientation] === encodedName) { + return DeviceOrientation[orientation]; + } + } + throw new Error("No such DeviceOrientation: " + encodedName); + } + + private decodeSystemUiMode(encodedSystemUiMode: string): SystemUiMode { + let mode: SystemUiMode = this.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: + return SystemUiMode.EDGE_TO_EDGE; + } + return SystemUiMode.EDGE_TO_EDGE; + + } + + private decodeSystemChromeStyle(encodedStyle: any): SystemChromeStyle { + let statusBarColor: number = null; + let statusBarIconBrightness: Brightness = null; + let systemStatusBarContrastEnforced: boolean = null; + let systemNavigationBarColor: number = null; + let systemNavigationBarIconBrightness: Brightness = null; + let systemNavigationBarDividerColor: number = null; + let systemNavigationBarContrastEnforced: boolean = null; + if(encodedStyle.statusBarColor != null) { + statusBarColor = encodedStyle.statusBarColor as number; + } + if(encodedStyle.statusBarIconBrightness != null) { + statusBarIconBrightness = + this.getBrightnessFromValue(encodedStyle.statusBarIconBrightness as string); + } + if(encodedStyle.systemStatusBarContrastEnforced != null) { + systemStatusBarContrastEnforced = encodedStyle.systemStatusBarContrastEnforced as boolean; + } + if(encodedStyle.systemNavigationBarColor != null) { + systemNavigationBarColor = encodedStyle.systemNavigationBarColor as number; + } + if(encodedStyle.systemNavigationBarIconBrightness != null) { + systemNavigationBarIconBrightness = + this.getBrightnessFromValue(encodedStyle.systemNavigationBarIconBrightness as string); + } + if(encodedStyle.systemNavigationBarDividerColor != null) { + systemNavigationBarDividerColor = encodedStyle.systemNavigationBarDividerColor as number; + } + if(encodedStyle.systemNavigationBarContrastEnforced != null) { + systemNavigationBarContrastEnforced = encodedStyle.systemNavigationBarContrastEnforced as boolean; + } + return new SystemChromeStyle( + statusBarColor, + statusBarIconBrightness, + systemStatusBarContrastEnforced, + systemNavigationBarColor, + systemNavigationBarIconBrightness, + systemNavigationBarDividerColor, + systemNavigationBarContrastEnforced + ); + } + +} +export enum HapticFeedbackType { + STANDARD = "STANDARD", + LIGHT_IMPACT = "HapticFeedbackType.lightImpact", + MEDIUM_IMPACT = "HapticFeedbackType.mediumImpact", + HEAVY_IMPACT = "HapticFeedbackType.heavyImpact", + SELECTION_CLICK = "HapticFeedbackType.selectionClick" +} + +export interface PlatformMessageHandler { + playSystemSound(soundType: SoundType): void; + + vibrateHapticFeedback(feedbackType: HapticFeedbackType): void; + + setPreferredOrientations(ohosOrientation: number): void; + + setApplicationSwitcherDescription(description: AppSwitcherDescription): void; + + showSystemOverlays(overlays: SystemUiOverlay[]): void; + + showSystemUiMode(mode: SystemUiMode): void; + + setSystemUiChangeListener(): void; + + restoreSystemUiOverlays(): void; + + setSystemUiOverlayStyle(systemUiOverlayStyle: SystemChromeStyle): void; + + popSystemNavigator(): void; + + getClipboardData(format: ClipboardContentFormat): string; + + setClipboardData(text: string): void; + + clipboardHasStrings(): boolean; + +} + +export enum ClipboardContentFormat { + PLAIN_TEXT = "text/plain", +} + +export enum SoundType { + CLICK = "SystemSoundType.click", + ALERT = "SystemSoundType.alert", +} + +export class AppSwitcherDescription { + public readonly color: number; + public readonly label: string; + + constructor(color: number, label: string) { + this.color = color; + this.label = label; + } +} + +export enum SystemUiOverlay { + TOP_OVERLAYS = "SystemUiOverlay.top", + BOTTOM_OVERLAYS = "SystemUiOverlay.bottom", +} + +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", +} + +export class SystemChromeStyle { + public readonly statusBarColor: number | null; + public readonly statusBarIconBrightness: Brightness | null; + public readonly systemStatusBarContrastEnforced: boolean | null; + public readonly systemNavigationBarColor: number | null; + public readonly systemNavigationBarIconBrightness: Brightness | null; + public readonly systemNavigationBarDividerColor: number | null; + public readonly systemNavigationBarContrastEnforced: boolean | null; + + 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; + } +} + +enum DeviceOrientation { + PORTRAIT_UP = "DeviceOrientation.portraitUp", + PORTRAIT_DOWN = "DeviceOrientation.portraitDown", + LANDSCAPE_LEFT = "DeviceOrientation.landscapeLeft", + LANDSCAPE_RIGHT = "DeviceOrientation.landscapeRight", +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ts new file mode 100644 index 0000000000..9c486edd88 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/RestorationChannel.ts @@ -0,0 +1,163 @@ +/* +* 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 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'; + +/** + * 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 {@code 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 Activity is created, this flag should be set to true because Android will + * only provide the restoration data to the Activity during the onCreate callback. + * + *

The current restoration data provided by the framework can be read via {@code + * 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 {@code setRestorationData} has been called. + * + *

If the engine never calls {@code setRestorationData} this flag must be set to false. If set + * to true, the engine must call {@code 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 {@code 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; + + // Holds the most current restoration data which may have been provided by the engine + // via "setRestorationData" or by the framework via the method channel. This is the data the + // framework should be restored to in case the app is terminated. + private restorationData: Int8Array; + private channel: MethodChannel; + private pendingFrameworkRestorationChannelRequest: MethodResult; + private engineHasProvidedData: boolean = false; + private frameworkHasRequestedData: boolean = false; + + 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.channel.setMethodCallHandler(this.handler); + } + + /** Obtain the most current restoration data that the framework has provided. */ + getRestorationData(): Int8Array { + return this.restorationData; + } + + /** Set the restoration data from which the framework will restore its state. */ + setRestorationData(data: Int8Array) { + this.engineHasProvidedData = true; + if (this.pendingFrameworkRestorationChannelRequest != null) { + // If their is a pending request from the framework, answer it. + this.pendingFrameworkRestorationChannelRequest.success(this.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", this.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 = null; + } + + private handler: MethodCallHandler = { + onMethodCall: (call: MethodCall, result: MethodResult) :void => { + const method = call.method; + const args = call.args; + switch (method) { + case "put": { + this.restorationData = args; + result.success(null); + break; + } + case "get": { + this.frameworkHasRequestedData = true; + if (this.engineHasProvidedData || !this.waitForRestorationData) { + result.success(this.packageData(this.restorationData)); + // 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.pendingFrameworkRestorationChannelRequest = result; + } + break; + } + default: { + result.notImplemented(); + break; + } + } + } + }; + + private packageData(data: Int8Array): Map { + const packaged: Map = new Map(); + packaged.set("enabled", true); + packaged.set("data", data); + return packaged; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ts new file mode 100644 index 0000000000..2b5021ca20 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SettingsChannel.ts @@ -0,0 +1,89 @@ +/* +* 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 BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; +import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; +import Log from '../../../util/Log'; +import DartExecutor from '../dart/DartExecutor'; + +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"; +export default class SettingsChannel { + private static CHANNEL_NAME = "flutter/settings"; + + private channel: BasicMessageChannel; + + constructor(dartExecutor: DartExecutor) { + this.channel = new BasicMessageChannel(dartExecutor, SettingsChannel.CHANNEL_NAME, JSONMessageCodec.INSTANCE); + } + + startMessage(): MessageBuilder { + return new MessageBuilder(this.channel); + } +} + +class MessageBuilder { + private channel: BasicMessageChannel; + private message: Map = new Map(); + + constructor(channel: BasicMessageChannel) { + this.channel = channel; + } + + setTextScaleFactor(textScaleFactor: Number): MessageBuilder { + this.message.set(TEXT_SCALE_FACTOR, textScaleFactor); + return this; + } + + setNativeSpellCheckServiceDefined(nativeSpellCheckServiceDefined: boolean): MessageBuilder { + this.message.set(NATIVE_SPELL_CHECK_SERVICE_DEFINED, nativeSpellCheckServiceDefined); + return this; + } + + setBrieflyShowPassword(brieflyShowPassword: boolean): MessageBuilder { + this.message.set(BRIEFLY_SHOW_PASSWORD, brieflyShowPassword); + return this; + } + + setAlwaysUse24HourFormat(alwaysUse24HourFormat: boolean): MessageBuilder { + this.message.set(ALWAYS_USE_24_HOUR_FORMAT, alwaysUse24HourFormat); + return this; + } + + setPlatformBrightness(platformBrightness: PlatformBrightness): MessageBuilder { + this.message.set(PLATFORM_BRIGHTNESS, platformBrightness); + return this; + } + + send(): void { + Log.i(TAG, "Sending message: \n" + + "textScaleFactor: " + + this.message.get(TEXT_SCALE_FACTOR) + + "alwaysUse24HourFormat: " + + this.message.get(ALWAYS_USE_24_HOUR_FORMAT) + + "platformBrightness: " + + this.message.get(PLATFORM_BRIGHTNESS)) + this.channel.send(Object.fromEntries(this.message)) + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SystemChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SystemChannel.ts new file mode 100644 index 0000000000..1c92edc718 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/SystemChannel.ts @@ -0,0 +1,39 @@ +/* +* 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 BasicMessageChannel from '../../../plugin/common/BasicMessageChannel'; +import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; +import Log from '../../../util/Log'; +import DartExecutor from '../dart/DartExecutor'; +const TAG: string = "SystemChannel"; + +/** + * TODO(mattcarroll): fill in javadoc for SystemChannel. + */ +export default class SystemChannel { + public channel: BasicMessageChannel; + + constructor(dartExecutor: DartExecutor) { + this.channel = new BasicMessageChannel(dartExecutor, "flutter/system", JSONMessageCodec.INSTANCE); + } + + public sendMemoryPressureWarning(): void { + Log.i(TAG, "Sending memory pressure warning to Flutter"); + let message: any = { + "type":"memoryPressure" + }; + this.channel.send(message); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TestChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TestChannel.ts new file mode 100644 index 0000000000..5387e7fdc3 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TestChannel.ts @@ -0,0 +1,36 @@ +/* +* 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 BasicMessageChannel, { Reply } from '../../../plugin/common/BasicMessageChannel'; +import JSONMessageCodec from '../../../plugin/common/JSONMessageCodec'; +import DartExecutor from '../dart/DartExecutor'; +import Log from '../../../util/Log'; + +const TAG = "TestChannel" + +export default class TestChannel { + private channel: BasicMessageChannel + + constructor(dartExecutor: DartExecutor) { + this.channel = new BasicMessageChannel(dartExecutor, "flutter/test", JSONMessageCodec.INSTANCE); + + this.channel.setMessageHandler({ + onMessage(call: String, reply: Reply): void { + Log.d(TAG, "receive msg = " + call); + reply.reply("收到消息啦:" + call); + } + }); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ts new file mode 100644 index 0000000000..225439d1fd --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/engine/systemchannels/TextInputChannel.ts @@ -0,0 +1,329 @@ +/* +* 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 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'; + +const TAG = "TextInputChannel"; + +export default class TextInputChannel { + private static CHANNEL_NAME = "flutter/textinput"; + public channel: MethodChannel; + private textInputMethodHandler: TextInputMethodHandler; + + constructor(dartExecutor: DartExecutor) { + this.channel = new MethodChannel(dartExecutor, TextInputChannel.CHANNEL_NAME, JSONMethodCodec.INSTANCE); + this.channel.setMethodCallHandler({ + onMethodCall: (call: MethodCall, result: MethodResult): void => { + 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 config: Configuration = null; + this.textInputMethodHandler.setClient(textInputClientId, config); + result.success(null); + break; + case "TextInput.requestAutofill": + case "TextInput.setPlatformViewClient": + case "TextInput.setEditingState": + this.textInputMethodHandler.setEditingState(TextEditState.fromJson(args)); + case "TextInput.setEditableSizeAndTransform": + case "TextInput.clearClient": + case "TextInput.sendAppPrivateCommand": + case "TextInput.finishAutofillContext": + } + } + }); + } + + + + setTextInputMethodHandler(textInputMethodHandler: TextInputMethodHandler): void { + this.textInputMethodHandler = textInputMethodHandler; + } + + requestExistingInputState(): void { + this.channel.invokeMethod("TextInputClient.requestExistingInputState", null); + } + + createEditingStateJSON(text: String, + selectionStart: number, + selectionEnd: number, + composingStart: number, + composingEnd: number): any { + let state = { + "text": text, + "selectionBase": selectionStart, + "selectionExtent": selectionEnd, + "composingBase": composingStart, + "composingExtent": composingEnd + } + 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]); + Log.d(TAG,"updateEditingState end"); + + } + + newline(inputClientId: number): void { + Log.d(TAG, "Sending 'newline' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.newline"]); + } + + go(inputClientId: number): void { + Log.d(TAG, "Sending 'go' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.go"]); + } + + search(inputClientId: number): void { + Log.d(TAG, "Sending 'search' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.search"]); + } + + send(inputClientId: number): void { + Log.d(TAG, "Sending 'send' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.send"]); + } + + done(inputClientId: number): void { + Log.d(TAG, "Sending 'done' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.done"]); + } + + next(inputClientId: number): void { + Log.d(TAG, "Sending 'next' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.next"]); + } + + previous(inputClientId: number): void { + Log.d(TAG, "Sending 'previous' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.previous"]); + } + + unspecifiedAction(inputClientId: number): void { + Log.d(TAG, "Sending 'unspecifiedAction' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.unspecifiedAction"]); + } + + commitContent(inputClientId: number): void { + Log.d(TAG, "Sending 'commitContent' message."); + this.channel.invokeMethod("TextInputClient.performAction", [inputClientId, "TextInputAction.commitContent"]); + } + + performPrivateCommand(inputClientId: number, action: string, data: any) { + + } + + + +} + + +export interface TextInputMethodHandler { + show(): void; + + hide(): void; + + requestAutofill(): void; + + finishAutofillContext(shouldSave: boolean): void; + + setClient(textInputClientId: number, configuration: Configuration): void; + + setPlatformViewClient(id: number, usesVirtualDisplay: boolean): void; + + setEditableSizeAndTransform(width: number, height: number, transform: number[]): void; + + setEditingState(editingState: TextEditState): void; + + clearClient(): void; + +} + +export class Configuration { + obscureText: boolean; + autocorrect: boolean; + enableSuggestions: boolean; + enableIMEPersonalizedLearning: boolean; + enableDeltaModel: boolean; + textCapitalization: TextCapitalization; + inputType:InputType; + inputAction: Number; + actionLabel: String; + //TODO: Autofill autofill; + contentCommitMimeTypes: String[]; + fields: Configuration[]; + + constructor(obscureText: boolean, + autocorrect: boolean, + enableSuggestions: boolean, + enableIMEPersonalizedLearning: boolean, + enableDeltaModel: boolean, + inputType: InputType, + inputAction: Number, + actionLabel: String, ) { + } + getTextCapitalizationFromValue(encodedName: string): TextCapitalization { + for (const key in TextCapitalization) { + if (TextCapitalization[key] === encodedName) { + return key; + } + } + throw new Error("No such TextCapitalization: " + encodedName); + } +} + +enum TextCapitalization { + CHARACTERS = "TextCapitalization.characters", + WORDS = "TextCapitalization.words", + SENTENCES = "TextCapitalization.sentences", + NONE = "TextCapitalization.none", +} + +export enum TextInputType { + TEXT = "TextInputType.text", + DATETIME = "TextInputType.datetime", + NAME = "TextInputType.name", + POSTAL_ADDRESS = "TextInputType.address", + NUMBER = "TextInputType.number", + PHONE = "TextInputType.phone", + MULTILINE = "TextInputType.multiline", + EMAIL_ADDRESS = "TextInputType.emailAddress", + URL = "TextInputType.url", + VISIBLE_PASSWORD = "TextInputType.visiblePassword", + NONE = "TextInputType.none", +} + +export class InputType { + type: TextInputType; + isSigned: boolean; + isDecimal: boolean; + + constructor(type: TextInputType, isSigned: boolean, isDecimal: boolean) { + this.type = type; + this.isSigned = isSigned; + this.isDecimal = isDecimal; + } + + static fromJson(json: any): InputType { + return new InputType(this.getTextInputTypeFromValue(json.name as string), + json.signed as boolean, json.decimal as boolean) + } + + static getTextInputTypeFromValue(encodedName: string): TextInputType{ + for(const key in TextInputType) { + if(TextInputType[key] == encodedName) { + return key; + } + } + throw new Error("No such TextInputType: " + encodedName); + } +} + +export class TextEditState { + private static TAG = "TextEditState"; + text: string; + selectionStart: number; + selectionEnd: number; + composingStart: number; + composingEnd: number; + + 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; + } + + hasComposing(): boolean { + return this.composingStart >= 0 && this.composingEnd > this.composingStart; + } + + static fromJson(textEditState: any): TextEditState { + return new TextEditState( + textEditState.text, + textEditState.selectionBase, + textEditState.selectionExtent, + textEditState.composingBase, + textEditState.composingExtent + ) + } + +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/ExclusiveAppComponent.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/ExclusiveAppComponent.ts new file mode 100644 index 0000000000..f7ecfc7d5e --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/ExclusiveAppComponent.ts @@ -0,0 +1,32 @@ +/* +* 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. +*/ + +export default interface ExclusiveAppComponent { + /** + * Called when another App Component is about to become attached to the {@link + * io.flutter.embedding.engine.FlutterEngine} this App Component is currently attached to. + * + *

This App Component's connections to the {@link io.flutter.embedding.engine.FlutterEngine} + * are still valid at the moment of this call. + */ + detachFromFlutterEngine(): void; + + /** + * Retrieve the App Component behind this exclusive App Component. + * + * @return The app component. + */ + getAppComponent(): T; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ts new file mode 100644 index 0000000000..efd35e82ba --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbility.ts @@ -0,0 +1,401 @@ +/* +* 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 UIAbility from '@ohos.app.ability.UIAbility'; +import window from '@ohos.window'; +import { FlutterAbilityDelegate, Host } from './FlutterAbilityDelegate'; +import Log from '../../util/Log'; +import FlutterEngine from '../engine/FlutterEngine'; +import FlutterShellArgs from '../engine/FlutterShellArgs'; +import FlutterAbilityLaunchConfigs from './FlutterAbilityLaunchConfigs'; +import common from '@ohos.app.ability.common'; +import Want from '@ohos.app.ability.Want'; +import display from '@ohos.display'; +import { FlutterPlugin } from '../engine/plugins/FlutterPlugin'; +import { AsyncCallback } from '@ohos.base'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + + +const TAG = "FlutterAbility"; +/** + * flutter ohos基础ability,请在让主ability继承自该类。 + * 该类主要职责: + * 1、持有FlutterAbilityDelegate并初始化; + * 2、生命周期传递; + */ +export class FlutterAbility extends UIAbility implements Host { + private delegate: FlutterAbilityDelegate; + private windowStage: window.WindowStage; + private mainWindow: window.Window; + private viewportMetrics = new ViewportMetrics(); + private displayInfo: display.Display; + + + /** + * onCreate + * 1、create and attach delegate + * 2、config windows transparent noNeed? + * 3、lifecycle.onCreate + * 4. setContentView() noNeed + */ + async onCreate(want, launchParam) { + Log.i(TAG, "bundleCodeDir=" + this.context.bundleCodeDir); + globalThis.flutterAbility = this + this.displayInfo = display.getDefaultDisplaySync() + this.viewportMetrics.devicePixelRatio = this.displayInfo.densityPixels + this.delegate = new FlutterAbilityDelegate(this); + await this.delegate.onAttach(this.context); + Log.i(TAG, 'onAttach end'); + this.delegate.platformPlugin.setUIAbilityContext(this.context); + this.delegate.onRestoreInstanceState(want); + this.delegate.sendSettings(); + + if (this.stillAttachedForEvent("onCreate")) { + this.delegate.onCreate(); + } + } + + onDestroy() { + if (this.stillAttachedForEvent("onDestroy")) { + this.delegate.onDestroy(); + } + } + + /** + * window状态改变回调 + * @param windowStage + */ + onWindowStageCreate(windowStage: window.WindowStage) { + this.windowStage = windowStage + try { + windowStage.on('windowStageEvent', (data) => { + let stageEventType: window.WindowStageEventType = data; + switch (stageEventType) { + case window.WindowStageEventType.SHOWN: // 切到前台 + Log.i(TAG, 'windowStage foreground.'); + break; + case window.WindowStageEventType.ACTIVE: // 获焦状态 + Log.i(TAG, 'windowStage active.'); + if (this.stillAttachedForEvent("onWindowFocusChanged")) { + this.delegate.onWindowFocusChanged(true); + } + break; + case window.WindowStageEventType.INACTIVE: // 失焦状态 + Log.i(TAG, 'windowStage inactive.'); + if (this.stillAttachedForEvent("onWindowFocusChanged")) { + this.delegate.onWindowFocusChanged(false); + } + break; + case window.WindowStageEventType.HIDDEN: // 切到后台 + Log.i(TAG, 'windowStage background.'); + break; + default: + break; + } + }); + + this.mainWindow = windowStage.getMainWindowSync() + this.mainWindow.on('windowSizeChange', (data) => { + this.onWindowPropertiesUpdated(); + }); + + this.mainWindow.on('avoidAreaChange', (data) => { + this.onWindowPropertiesUpdated(); + }); + + this.mainWindow.on('keyboardHeightChange', (data) => { + this.onWindowPropertiesUpdated(); + }); + + this.loadContent() + } catch (exception) { + Log.e(TAG, 'Failed to enable the listener for window stage event changes. Cause:' + JSON.stringify(exception)); + } + } + + loadContent() { + if (this.windowStage != null && this.stillAttachedForEvent("loadContent")) { + Log.i(TAG, 'loadContent'); + this.windowStage.loadContent('pages/Index', (err, data) => { + if (err.code) { + Log.e(TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + this.onWindowPropertiesUpdated(); + Log.i(TAG, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); + }); + if (this.stillAttachedForEvent("onWindowStageCreate")) { + this.delegate.onWindowStageCreate(); + } + this.delegate.getFlutterNapi().updateRefreshRate(this.displayInfo.refreshRate) + this.onFlutterEngineReady() + } + } + + onFlutterEngineReady(): void { + + } + + private updateViewportMetrics() { + this.delegate.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, + new Array(0), + new Array(0), + new Array(0)) + } + + onWindowStageDestroy() { + if (this.stillAttachedForEvent("onWindowStageDestroy")) { + this.delegate.onWindowStageDestroy(); + } + } + + onForeground() { + if (this.stillAttachedForEvent("onForeground")) { + this.delegate.onForeground(); + } + } + + onBackground() { + if (this.stillAttachedForEvent("onBackground")) { + this.delegate.onBackground(); + } + } + + release() { + if (this.delegate != null) { + this.delegate.release(); + this.delegate = null; + } + } + + /** + * host所有实现方法开始======start + */ + + getAbility(): UIAbility { + return this; + } + + shouldDispatchAppLifecycleState(): boolean { + return true; + } + + provideFlutterEngine(context: common.Context): FlutterEngine { + return null; + } + + configureFlutterEngine(flutterEngine: FlutterEngine) { + + } + + cleanUpFlutterEngine(flutterEngine: FlutterEngine) { + + } + + getFlutterShellArgs(): FlutterShellArgs { + return FlutterShellArgs.fromWant(this.getWant()); + } + + 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() + } + + detachFromFlutterEngine() { + if (this.delegate != null) { + this.delegate.onDetach(); + } + } + + popSystemNavigator(): boolean { + return false; + } + + shouldAttachEngineToActivity(): boolean { + return true; + } + + getDartEntrypointLibraryUri(): string { + // TODO form metaData read + return null; + } + + getAppBundlePath(): string { + return null; + } + + getDartEntrypointFunctionName(): string { + if (this.launchWant.parameters[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT]) { + return this.launchWant.parameters[FlutterAbilityLaunchConfigs.EXTRA_DART_ENTRYPOINT] as string; + } + // TODO form metaData read + return FlutterAbilityLaunchConfigs.DEFAULT_DART_ENTRYPOINT + } + + getInitialRoute(): string { + if (this.launchWant.parameters[FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE]) { + return this.launchWant.parameters[FlutterAbilityLaunchConfigs.EXTRA_INITIAL_ROUTE] as string; + } + // TODO form metaData read + return null + } + + getWant(): Want { + return this.launchWant; + } + + shouldDestroyEngineWithHost(): boolean { + //TODO shouldDestroyEngineWithHost + return true; + } + + 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) { + // Prevent overwriting the existing state in a cached engine with restoration state. + return false; + } + return true; + } + + getCachedEngineId(): string { + return this.launchWant.parameters[FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_ID] as string + } + + getCachedEngineGroupId(): string { + return this.launchWant.parameters[FlutterAbilityLaunchConfigs.EXTRA_CACHED_ENGINE_GROUP_ID] as string + } + + /** + * host所有实现方法结束======end + */ + 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; + } + + addPlugin(plugin: FlutterPlugin): void { + if (this.delegate != null) { + this.delegate.addPlugin(plugin) + } + } + + removePlugin(plugin: FlutterPlugin): void { + if (this.delegate != null) { + this.delegate.removePlugin(plugin) + } + } + + private onWindowPropertiesUpdated(){ + if (!this.delegate.isAttached) { + return; + } + let systemAvoidArea = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM); + let gestureAvoidArea = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM_GESTURE); + let keyboardAvoidArea = this.mainWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_KEYBOARD); + const properties = this.mainWindow.getWindowProperties(); + this.viewportMetrics.physicalWidth = properties.windowRect.width; + this.viewportMetrics.physicalHeight = properties.windowRect.height; + + this.viewportMetrics.physicalViewPaddingTop = systemAvoidArea.topRect.height + this.viewportMetrics.physicalViewPaddingLeft = systemAvoidArea.leftRect.width + this.viewportMetrics.physicalViewPaddingBottom = systemAvoidArea.bottomRect.height + this.viewportMetrics.physicalViewPaddingRight = systemAvoidArea.rightRect.width + + this.viewportMetrics.physicalViewInsetTop = keyboardAvoidArea.topRect.height + this.viewportMetrics.physicalViewInsetLeft = keyboardAvoidArea.leftRect.width + this.viewportMetrics.physicalViewInsetBottom = keyboardAvoidArea.bottomRect.height + this.viewportMetrics.physicalViewInsetRight = keyboardAvoidArea.rightRect.width + + this.viewportMetrics.systemGestureInsetTop = gestureAvoidArea.topRect.height + this.viewportMetrics.systemGestureInsetLeft = gestureAvoidArea.leftRect.width + this.viewportMetrics.systemGestureInsetBottom = gestureAvoidArea.bottomRect.height + this.viewportMetrics.systemGestureInsetRight = gestureAvoidArea.rightRect.width + + this.updateViewportMetrics() + } + + onMemoryLevel(level: AbilityConstant.MemoryLevel): void { + Log.i(TAG, 'onMemoryLevel: ' + level); + if (level === AbilityConstant.MemoryLevel.MEMORY_LEVEL_CRITICAL) { + this.delegate.onLowMemory(); + } + } + + getWindowId(callback: AsyncCallback): void { + if (callback === null) { + return; + } + try { + window.getLastWindow(this.context, (error, win) => { + if (error.code) { + callback(error, -1); + return; + } + let windowId = win.getWindowProperties().id; + callback(error, windowId); + }); + } catch (err) { + Log.e(TAG, "get window id error!"); + callback(err, -1); + } + } +} + +class ViewportMetrics { + devicePixelRatio: number = 1.0; + physicalWidth: number = 0; + physicalHeight: number = 0; + physicalViewPaddingTop: number = 0; + physicalViewPaddingRight: number = 0; + physicalViewPaddingBottom: number = 0; + physicalViewPaddingLeft: number = 0; + physicalViewInsetTop: number = 0; + physicalViewInsetRight: number = 0; + physicalViewInsetBottom: number = 0; + physicalViewInsetLeft: number = 0; + systemGestureInsetTop: number = 0; + systemGestureInsetRight: number = 0; + systemGestureInsetBottom: number = 0; + systemGestureInsetLeft: number = 0; + physicalTouchSlop = -1; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityDelegate.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityDelegate.ts new file mode 100644 index 0000000000..8325e3eb58 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityDelegate.ts @@ -0,0 +1,434 @@ +/* +* 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 FlutterEngineConfigurator from './FlutterEngineConfigurator'; +import FlutterEngineProvider from './FlutterEngineProvider'; +import FlutterEngine from '../engine/FlutterEngine'; +import PlatformPlugin, { PlatformPluginDelegate } from '../../plugin/PlatformPlugin'; +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 TextInputPlugin from '../../plugin/editing/TextInputPlugin'; +import { FlutterPlugin } from '../engine/plugins/FlutterPlugin'; +import FlutterEngineCache from '../engine/FlutterEngineCache'; +import FlutterEngineGroupCache from '../engine/FlutterEngineGroupCache'; +import FlutterEngineGroup, { Options } from '../engine/FlutterEngineGroup'; +import MouseCursorPlugin, { MouseCursorViewDelegate } from '../../plugin/mouse/MouseCursorPlugin'; +import Settings from './Settings'; + +const TAG = "FlutterAbilityDelegate"; +const PLUGINS_RESTORATION_BUNDLE_KEY = "plugins"; +/** + * 主要职责: + * 1、初始化engine + * 2、处理ability生命周期回调 + */ +class FlutterAbilityDelegate implements ExclusiveAppComponent { + private host: Host; + private flutterEngine: FlutterEngine; + platformPlugin: PlatformPlugin; + private context: common.Context; + private textInputPlugin: TextInputPlugin; + private isFlutterEngineFromHost: boolean; + private engineGroup: FlutterEngineGroup; + private mouseCursorPlugin: MouseCursorPlugin; + private settings: Settings; + + constructor(host: Host) { + this.host = host; + } + + /** + * 是否还attach在ability上 + */ + isAttached = false; + + async onAttach(context: common.Context): Promise { + this.context = context; + this.ensureAlive(); + if (this.flutterEngine == null) { + await this.setupFlutterEngine(); + } + //shouldAttachEngineToActivity + if (this.host.shouldAttachEngineToActivity()) { + // 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); + } + + //providePlatformPlugin + + //configureFlutterEngine + this.isAttached = true; + Log.d(TAG, "onAttach end start loadcontent") + this.host.loadContent() + this.textInputPlugin = new TextInputPlugin(this.flutterEngine.getTextInputChannel()); + this.platformPlugin = new PlatformPlugin(this.flutterEngine.getPlatformChannel(), this.context); + this.mouseCursorPlugin = new MouseCursorPlugin(this.host, this.flutterEngine.getMouseCursorChannel()); + this.settings = new Settings(this.flutterEngine.getSettingsChannel()); + this.flutterEngine.getSystemLanguages(); + } + + /** + * 加载app.so资源或者snapshot + */ + private doInitialFlutterViewRun(): void { + let initialRoute = this.host.getInitialRoute(); + if (initialRoute == 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 null; + } + + + /** + * 通过参数,配置flutterEngine + * @param want + */ + onRestoreInstanceState(want: Want) { + + } + + /** + * 初始化flutterEngine + */ + async 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.isFlutterEngineFromHost = 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. + this.flutterEngine = this.host.provideFlutterEngine(this.context); + if (this.flutterEngine != null) { + this.isFlutterEngineFromHost = 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 + + "'"); + } + + this.flutterEngine = await flutterEngineGroup.createAndRunEngineByOptions(this.addEntrypointOptions(new Options(this.context))); + this.isFlutterEngineFromHost = 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) { + group = new FlutterEngineGroup(); + await group.checkLoader(this.context, this.host.getFlutterShellArgs().toArray()); + } + this.flutterEngine = await group.createAndRunEngineByOptions(this.addEntrypointOptions(new Options(this.context) + .setAutomaticallyRegisterPlugins(false).setWaitForRestorationData(this.host.shouldRestoreAndSaveState()))); + this.isFlutterEngineFromHost = false; + } + + 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, null, this.host.getDartEntrypointFunctionName()); + let initialRoute = this.host.getInitialRoute(); + if (initialRoute == 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()); + } + + /** + * 释放所有持有对象 + */ + release() { + this.host = null; + this.flutterEngine = null; + } + + onDetach() { + //todo + if (this.host.shouldAttachEngineToActivity()) { + // Notify plugins that they are no longer attached to an Activity. + Log.d(TAG, "Detaching FlutterEngine from the Ability"); + this.flutterEngine.getAbilityControlSurface().detachFromAbility(); + } + } + + onLowMemory(): void { + this.getFlutterNapi().notifyLowMemoryWarning(); + this.flutterEngine.getSystemChannel().sendMemoryPressureWarning(); + } + + /** + * 生命周期回调 + */ + + onCreate() { + this.ensureAlive(); + if (this.shouldDispatchAppLifecycleState()) { + this.flutterEngine.getLifecycleChannel().appIsInactive(); + } + } + + onDestroy() { + this.ensureAlive(); + if (this.shouldDispatchAppLifecycleState()) { + this.flutterEngine.getLifecycleChannel().appIsDetached(); + } + this.textInputPlugin.detach(); + } + + onWindowStageCreate() { + if (this.shouldDispatchAppLifecycleState()) { + this.flutterEngine.getLifecycleChannel().appIsResumed(); + } + this.ensureAlive(); + this.doInitialFlutterViewRun(); + } + + onWindowStageDestroy() { + + } + + onWindowFocusChanged(hasFocus: boolean):void { + if (this.shouldDispatchAppLifecycleState()) { + this.flutterEngine.getAbilityControlSurface().onWindowFocusChanged(hasFocus); + if (hasFocus) { + this.flutterEngine.getLifecycleChannel().aWindowIsFocused(); + } else { + this.flutterEngine.getLifecycleChannel().noWindowsAreFocused(); + } + } + } + + onForeground() { + this.ensureAlive(); + if (this.shouldDispatchAppLifecycleState()) { + this.flutterEngine.getLifecycleChannel().appIsResumed(); + } + } + + onBackground() { + if (this.shouldDispatchAppLifecycleState()) { + this.flutterEngine.getLifecycleChannel().appIsPaused(); + } + } + + /** + * 生命周期回调结束 + */ + + shouldDispatchAppLifecycleState(): boolean { + return this.host.shouldDispatchAppLifecycleState() && this.isAttached; + } + + ensureAlive() { + if (this.host == null) { + throw new Error("Cannot execute method on a destroyed FlutterAbilityDelegate."); + } + } + + getFlutterNapi() { + return this.flutterEngine.getFlutterNapi() + } + + detachFromFlutterEngine() { + if (this.host.shouldDestroyEngineWithHost()) { + // The host owns the engine and should never have its engine taken by another exclusive + // activity. + throw new Error( + "The internal FlutterEngine created by " + + this.host + + " has been attached to by another activity. To persist a FlutterEngine beyond the " + + "ownership of this ablity, 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 + // activity. + this.host.detachFromFlutterEngine(); + } + + getAppComponent(): UIAbility { + const ability = this.host.getAbility(); + if (ability == null) { + throw new Error( + "FlutterActivityAndFragmentDelegate's getAppComponent should only " + + "be queried after onAttach, when the host's ability should always be non-null"); + } + return ability; + } + + 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 Activity."); + } + } + + onSaveState(reason: AbilityConstant.StateType, wantParam: { [key: string]: Object; }): AbilityConstant.OnSaveResult { + Log.i(TAG, "onSaveInstanceState. Giving framework and plugins an opportunity to save state."); + this.ensureAlive(); + // TODO shouldRestoreAndSaveState + if (this.host.shouldAttachEngineToActivity()) { + const plugins = {} + const result = this.flutterEngine.getAbilityControlSurface().onSaveState(reason, plugins); + wantParam[PLUGINS_RESTORATION_BUNDLE_KEY] = plugins; + return result + } + return AbilityConstant.OnSaveResult.ALL_REJECT + } + + addPlugin(plugin: FlutterPlugin): void { + this.flutterEngine.getPlugins().add(plugin) + } + + removePlugin(plugin: FlutterPlugin): void { + this.flutterEngine.getPlugins().remove(plugin.getUniqueClassName()) + } + + sendSettings(): void { + this.settings.sendSettings() + } +} + + +/** + * FlutterAbility句柄 + */ +interface Host extends FlutterEngineProvider, FlutterEngineConfigurator, PlatformPluginDelegate, MouseCursorViewDelegate { + + getAbility(): UIAbility; + + loadContent():void; + + shouldDispatchAppLifecycleState(): boolean; + + detachFromFlutterEngine(); + + shouldAttachEngineToActivity(): boolean; + + getCachedEngineId(): string; + + getCachedEngineGroupId(): string; + + /** + * 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; +} + +export { Host, FlutterAbilityDelegate } \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ts new file mode 100644 index 0000000000..996f455b9d --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterAbilityLaunchConfigs.ts @@ -0,0 +1,46 @@ +/* +* 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. +*/ + +/** The mode of the background of a Flutter {@code Activity}, either opaque or transparent. */ +enum BackgroundMode { + /** Indicates a FlutterActivity with an opaque background. This is the default. */ + opaque, + /** Indicates a FlutterActivity with a transparent background. */ + transparent +} + +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"; + // Intent extra arguments. + static EXTRA_DART_ENTRYPOINT = "dart_entrypoint"; + 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 +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ts new file mode 100644 index 0000000000..7953cad083 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineConfigurator.ts @@ -0,0 +1,23 @@ +/* +* 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 FlutterEngine from '../engine/FlutterEngine'; + +export default interface FlutterEngineConfigurator { + + configureFlutterEngine(flutterEngine: FlutterEngine); + + cleanUpFlutterEngine(flutterEngine: FlutterEngine); +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineProvider.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineProvider.ts new file mode 100644 index 0000000000..1bdd7b6a80 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterEngineProvider.ts @@ -0,0 +1,21 @@ +/* +* 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 FlutterEngine from '../engine/FlutterEngine'; +import common from '@ohos.app.ability.common'; + +export default interface FlutterEngineProvider { + provideFlutterEngine(context: common.Context): FlutterEngine; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterPage.ets b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterPage.ets new file mode 100644 index 0000000000..d6a123491d --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/FlutterPage.ets @@ -0,0 +1,34 @@ +/* +* 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. +*/ + +/** + * 基础page组件,承载XComponent组件 + */ +@Component +export struct FlutterPage { + @State message: string = 'Hello World' + private context = null; + + build() { + Column() { + XComponent({ id: 'flutterXComponent', type: 'texture', libraryname: 'flutter' }) + .onLoad((context) => { + this.context = context; + }) + .onDestroy(() => { + }) + } + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/Settings.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/Settings.ts new file mode 100644 index 0000000000..a435d2a45c --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/embedding/ohos/Settings.ts @@ -0,0 +1,35 @@ +/* +* 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 SettingsChannel, { PlatformBrightness } from '../engine/systemchannels/SettingsChannel' +import I18n from '@ohos.i18n' + +export default class Settings { + settingsChannel: SettingsChannel; + + constructor(settingsChannel: SettingsChannel) { + this.settingsChannel = settingsChannel; + } + + sendSettings(): void { + this.settingsChannel.startMessage() + .setAlwaysUse24HourFormat(I18n.System.is24HourClock()) + .setTextScaleFactor(1.0) + .setNativeSpellCheckServiceDefined(false) + .setBrieflyShowPassword(false) + .setPlatformBrightness(PlatformBrightness.LIGHT) + .send(); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/PlatformPlugin.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/PlatformPlugin.ts new file mode 100644 index 0000000000..c05992dc51 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/PlatformPlugin.ts @@ -0,0 +1,298 @@ +/* +* 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 PlatformChannel, { + AppSwitcherDescription, + Brightness, + ClipboardContentFormat, + HapticFeedbackType, + PlatformMessageHandler, + SoundType, + SystemChromeStyle, + SystemUiMode, + SystemUiOverlay +} from '../embedding/engine/systemchannels/PlatformChannel'; +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'; + +/** + * ohos实现platform plugin + */ +export default class PlatformPlugin { + private static TAG = "PlatformPlugin"; + private platformChannel: PlatformChannel; + private platformPluginDelegate: PlatformPluginDelegate; + private context: common.Context; + private windowClass: window.Window = null; + private currentTheme: SystemChromeStyle = null; + private showBarOrNavigation: ('status' | 'navigation')[] = ['status','navigation']; + private uiAbilityContext:common.UIAbilityContext = null; + private callbackId :number = null; + private applicationContext: common.ApplicationContext = null; + + constructor(platformChannel: PlatformChannel, context: common.Context, platformPluginDelegate?: PlatformPluginDelegate) { + this.platformChannel = platformChannel; + this.context = context; + this.applicationContext = this.context.getApplicationContext(); + + try { + window.getLastWindow(this.context, (err,data) => { + if(err.code) { + Log.e(PlatformPlugin.TAG, "Failed to obtain the top window. Cause: " + JSON.stringify(err)); + return; + } + this.windowClass = data; + }); + } catch (err) { + Log.e(PlatformPlugin.TAG,"Failed to obtain the top window. Cause: " + JSON.stringify(err)); + } + this.platformPluginDelegate = platformPluginDelegate; + + this.platformChannel.setPlatformMessageHandler({ + playSystemSound:(soundType: SoundType): void => { + + }, + + vibrateHapticFeedback: (feedbackType: HapticFeedbackType): void => { + switch (feedbackType) { + case HapticFeedbackType.STANDARD: + vibrator.startVibration({type:'time',duration:100}, + {id:0, usage:'touch'}); + break; + case HapticFeedbackType.LIGHT_IMPACT: + vibrator.startVibration({type:'time',duration:100}, + {id:0, usage:'notification'}).then(); + break; + case HapticFeedbackType.MEDIUM_IMPACT: + vibrator.startVibration({type:'time',duration:100}, + {id:0, usage:'ring'}); + break; + case HapticFeedbackType.HEAVY_IMPACT: + vibrator.startVibration({type:'time',duration:100}, + {id:0, usage:'alarm'}); + break; + case HapticFeedbackType.SELECTION_CLICK: + vibrator.startVibration({type:'time',duration:100}, + {id:0, usage:'physicalFeedback'}); + break; + } + }, + + setPreferredOrientations:(ohosOrientation: number): void => { + Log.d(PlatformPlugin.TAG,"ohosOrientation: " + ohosOrientation); + this.windowClass.setPreferredOrientation(ohosOrientation); + }, + + setApplicationSwitcherDescription:(description: AppSwitcherDescription): void => { + //TODO:The Flutter application would like to be displayed in app switcher with the visual + // representation described in the given {@code description}. + }, + + showSystemOverlays:(overlays: SystemUiOverlay[]): void => { + this.setSystemChromeEnabledSystemUIOverlays(overlays); + }, + + showSystemUiMode:(mode: SystemUiMode): void => { + this.setSystemChromeEnabledSystemUIMode(mode); + }, + + setSystemUiChangeListener:(): void => { + this.setSystemChromeChangeListener(); + }, + + restoreSystemUiOverlays:(): void => { + this.updateSystemUiOverlays(); + }, + + setSystemUiOverlayStyle:(systemUiOverlayStyle: SystemChromeStyle): void => { + Log.d(PlatformPlugin.TAG, "systemUiOverlayStyle:" + JSON.stringify(systemUiOverlayStyle)); + this.setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle); + }, + + popSystemNavigator:(): void => { + this.popSystemNavigator(); + }, + + getClipboardData:(format: ClipboardContentFormat): string => { + return this.getClipboardData(format); + }, + + setClipboardData:(text: string): void => { + this.setClipboardData(text); + }, + + clipboardHasStrings:(): boolean => { + return; + } + }); + } + + private getClipboardData(format: ClipboardContentFormat): string { + return; + } + + private setClipboardData(text: string): void { + let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN,text); + let clipboard = pasteboard.getSystemPasteboard(); + clipboard.setData(pasteData); + } + + private setSystemChromeEnabledSystemUIMode(mode: SystemUiMode): void { + Log.d(PlatformPlugin.TAG,"mode: " + mode); + let uiConfig: ('status' | 'navigation')[] = []; + if(mode == SystemUiMode.LEAN_BACK) { + //全屏显示,通过点击显示器上的任何位置都可以显示状态和导航栏 + this.windowClass.setWindowLayoutFullScreen(false); + + } else if (mode == SystemUiMode.IMMERSIVE) { + //全屏显示,通过在显示器边缘的滑动手势可以显示状态和导航栏,应用程序不会接收到此手势 + this.windowClass.setWindowLayoutFullScreen(true); + + } else if (mode == SystemUiMode.IMMERSIVE_STICKY) { + //全屏显示,通过在显示器边缘的滑动手势可以显示状态和导航栏,此手势由应用程序接收 + this.windowClass.setWindowLayoutFullScreen(true); + + }else if (mode == SystemUiMode.EDGE_TO_EDGE) { + //全屏显示,在应用程序上呈现状态和导航元素 + this.windowClass.setWindowLayoutFullScreen(false); + uiConfig = ['status','navigation']; + + } else { + return; + } + this.showBarOrNavigation = uiConfig; + this.updateSystemUiOverlays(); + } + + private 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.updateSystemUiOverlays(); + + } + + private setSystemChromeSystemUIOverlayStyle(systemChromeStyle: SystemChromeStyle): void { + let isStatusBarLightIconValue: boolean = false; + let statusBarColorValue:string = null; + let statusBarContentColorValue: string = null; + let navigationBarColorValue: string = null; + let isNavigationBarLightIconValue: boolean = false; + let navigationBarContentColorValue: string = null; + if(systemChromeStyle.statusBarIconBrightness != null) { + switch (systemChromeStyle.statusBarIconBrightness) { + case Brightness.DARK: + isStatusBarLightIconValue = false; + break; + case Brightness.LIGHT: + isStatusBarLightIconValue = true; + break; + } + } + + if(systemChromeStyle.statusBarColor != null) { + statusBarColorValue = "#" + systemChromeStyle.statusBarColor.toString(16); + } + + if(systemChromeStyle.systemStatusBarContrastEnforced != null) { + + } + + if(systemChromeStyle.systemNavigationBarIconBrightness != null) { + switch (systemChromeStyle.systemNavigationBarIconBrightness) { + case Brightness.DARK: + isNavigationBarLightIconValue = true; + break; + case Brightness.LIGHT: + isNavigationBarLightIconValue = false; + } + } + + if(systemChromeStyle.systemNavigationBarColor != null) { + navigationBarColorValue = "#" + systemChromeStyle.systemNavigationBarColor.toString(16); + } + + if(systemChromeStyle.systemNavigationBarContrastEnforced != null) { + + } + this.currentTheme = systemChromeStyle; + let systemBarProperties = { + statusBarColor: statusBarColorValue, + isStatusBarLightIcon: isStatusBarLightIconValue, + statusBarContentColor: statusBarContentColorValue, + navigationBarColor: navigationBarColorValue, + isNavigationBarLightIcon: isNavigationBarLightIconValue, + navigationBarContentColor: navigationBarContentColorValue, + } + Log.d(PlatformPlugin.TAG, "systemBarProperties: "+JSON.stringify(systemBarProperties)); + this.windowClass.setWindowSystemBarProperties(systemBarProperties); + } + + private popSystemNavigator(): void { + if(this.platformPluginDelegate != null && this.platformPluginDelegate.popSystemNavigator()) { + return; + } + if(this.uiAbilityContext != null) { + this.uiAbilityContext.terminateSelf(); + } + } + + updateSystemUiOverlays(): void { + this.windowClass.setWindowSystemBarEnable(this.showBarOrNavigation); + if(this.currentTheme != null) { + this.setSystemChromeSystemUIOverlayStyle(this.currentTheme); + } + } + + setUIAbilityContext(context: common.UIAbilityContext): void { + this.uiAbilityContext = context; + } + + setSystemChromeChangeListener(): void { + //TODO: Set up a listener to notify the framework when the system ui has changed. + // + if(this.callbackId == null && this.applicationContext != null) { + let that = this; + this.callbackId = this.applicationContext.on('environment',{ + onConfigurationUpdated(config) { + Log.d(PlatformPlugin.TAG, "onConfigurationUpdated: " + that.showBarOrNavigation); + that.platformChannel.systemChromeChanged(that.showBarOrNavigation.includes('status')); + }, + onMemoryLevel(level) { + } + }) + } + } + +} + +export interface PlatformPluginDelegate { + popSystemNavigator(): boolean; +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BasicMessageChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BasicMessageChannel.ts new file mode 100644 index 0000000000..d6ef6c6c26 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BasicMessageChannel.ts @@ -0,0 +1,170 @@ +/* +* 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 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'; +/** + * 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 Java 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 Java type of messages sent and received + * is {@code 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 { + public static TAG = "BasicMessageChannel#"; + public static CHANNEL_BUFFERS_CHANNEL = "dev.flutter/channel-buffers"; + private messenger: BinaryMessenger; + private name: string; + private codec: MessageCodec; + private taskQueue: TaskQueue; + + constructor(messenger: BinaryMessenger, name: string, codec: MessageCodec, taskQueue?: TaskQueue) { + this.messenger = messenger + this.name = name + this.codec = codec + this.taskQueue = taskQueue + } + + /** + * 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): 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 IncomingMessageHandler(handler, this.codec), this.taskQueue); + } else { + 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. + */ + resizeChannelBuffer(newSize: number): void { + MessageChannelUtils.resizeChannelBuffer(this.messenger, this.name, newSize); + } +} + + +export interface Reply { + /** + * Handles the specified message reply. + * + * @param reply the reply, possibly null. + */ + reply(reply: T): void; +} + +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; +} + +class IncomingReplyHandler implements BinaryReply { + private callback: (reply: T)=>void; + private codec: MessageCodec + + constructor(callback:(reply: T)=>void, codec: MessageCodec) { + this.callback = callback + this.codec = codec + } + + reply(reply: ArrayBuffer) { + try { + this.callback(this.codec.decodeMessage(reply)); + } catch (e) { + Log.e(BasicMessageChannel.TAG, "Failed to handle message reply", e); + } + } +} + +class IncomingMessageHandler implements BinaryMessageHandler { + private handler: MessageHandler + private codec: MessageCodec + + constructor(handler: MessageHandler, codec: MessageCodec) { + this.handler = handler; + this.codec = codec + } + + 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(null); + } + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryCodec.ts new file mode 100644 index 0000000000..578436417d --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryCodec.ts @@ -0,0 +1,49 @@ +/* +* 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 MessageCodec from './MessageCodec'; + +/** + * A {@link MessageCodec} using unencoded binary messages, represented as {@link ByteBuffer}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; + static readonly INSTANCE_DIRECT = new BinaryCodec(true); + + constructor(returnsDirectByteBufferFromDecoding: boolean) { + this.returnsDirectByteBufferFromDecoding = returnsDirectByteBufferFromDecoding; + } + + encodeMessage(message: ArrayBuffer): ArrayBuffer { + return message + } + + decodeMessage(message: ArrayBuffer): ArrayBuffer { + if (message == null) { + return message; + } else if (this.returnsDirectByteBufferFromDecoding) { + return message; + } else { + return message.slice(0, message.byteLength); + } + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryMessenger.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryMessenger.ts new file mode 100644 index 0000000000..91476c1672 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/BinaryMessenger.ts @@ -0,0 +1,159 @@ +/* +* 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. +*/ + +/** + * 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. + */ +export interface TaskQueue {} + +/** Options that control how a TaskQueue should operate and be created. */ +export class TaskQueueOptions { + private isSerial = true; + + getIsSerial() { + return this.isSerial; + } + + setIsSerial(isSerial: boolean): TaskQueueOptions { + this.isSerial = isSerial; + 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, a direct-allocated {@link ByteBuffer} or null. Senders of + * outgoing replies must place the reply bytes between position zero and current position. + * Reply receivers can read from the buffer directly. + */ + reply(reply: ArrayBuffer): 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 ByteBuffer} 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. + * + *

{@code 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 abstract class BinaryMessenger { + //makeBackgroundTaskQueue() + makeBackgroundTaskQueue(options?: TaskQueueOptions): TaskQueue { + throw new Error("makeBackgroundTaskQueue not implemented.") + } + + /** + * Sends a binary message to the Flutter application. + * + * @param channel the name {@link String} of the logical channel used for the message. + * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message + * bytes between position zero and current position, or null. + */ + abstract send(channel: String, message: ArrayBuffer): 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 {@link String} of the logical channel used for the message. + * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message + * bytes between position zero and current position, or null. + * @param callback a {@link BinaryReply} callback invoked when the Flutter application responds to + * the message, possibly null. + */ + abstract send(channel: String, message: ArrayBuffer, callback?: BinaryReply): 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 {@link String} 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. + */ + //setMessageHandler(channel: String, handler: BinaryMessageHandler) + setMessageHandler(channel: String, handler: BinaryMessageHandler, taskQueue?: TaskQueue): void { + // TODO(92582): Remove default implementation when it is safe for Google Flutter users. + if (taskQueue != null) { + throw new Error("setMessageHandler called with nonnull taskQueue is not supported.") + } + } + + /** + * Enables the ability to queue messages received from Dart. + * + *

This is useful when there are pending channel handler registrations. For example, Dart may + * be initialized concurrently, and prior to the registration of the channel handlers. This + * implies that Dart may start sending messages while plugins are being registered. + */ + enableBufferingIncomingMessages(): void { + throw new Error("enableBufferingIncomingMessages not implemented."); + } + + /** + * Disables the ability to queue messages received from Dart. + * + *

This can be used after all pending channel handlers have been registered. + */ + disableBufferingIncomingMessages(): void { + throw new Error("disableBufferingIncomingMessages not implemented."); + } +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/FlutterException.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/FlutterException.ts new file mode 100644 index 0000000000..e8e8d40354 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/FlutterException.ts @@ -0,0 +1,28 @@ +/* +* 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. +*/ + +export default class FlutterException implements Error { + stack?: string; + message: string; + name: string; + code: string; + details: any + + 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMessageCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMessageCodec.ts new file mode 100644 index 0000000000..7d1167dd0e --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMessageCodec.ts @@ -0,0 +1,52 @@ +/* +* 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 MessageCodec from './MessageCodec'; +import StringCodec from './StringCodec'; + +/** + * 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. + * + *

Supports the same Java values as {@link JSONObject#wrap(Object)}. + * + *

On the Dart side, JSON messages are handled by the JSON facilities of the dart:convert package. + */ +export default class JSONMessageCodec implements MessageCodec { + static INSTANCE = new JSONMessageCodec(); + + encodeMessage(message: any): ArrayBuffer { + if (message == null) { + return null; + } + return StringCodec.INSTANCE.encodeMessage(JSON.stringify(message)); + } + + decodeMessage(message: ArrayBuffer): any { + if (message == null) { + return null; + } + try { + const jsonStr = StringCodec.INSTANCE.decodeMessage(message); + return JSON.parse(jsonStr); + } catch (e) { + throw new Error("Invalid JSON"); + } + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMethodCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMethodCodec.ts new file mode 100644 index 0000000000..42a4f1ac6b --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/JSONMethodCodec.ts @@ -0,0 +1,96 @@ +/* +* 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 Log from '../../util/Log'; + +import ToolUtils from '../../util/ToolUtils'; +import FlutterException from './FlutterException'; +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 { + static INSTANCE = new JSONMethodCodec(); + + encodeMethodCall(methodCall: MethodCall): ArrayBuffer { + try { + const map = { + "method": methodCall.method, "args": methodCall.args + } + return JSONMessageCodec.INSTANCE.encodeMessage(map); + } catch (e) { + throw new Error("Invalid JSON"); + } + } + + decodeMethodCall(message: ArrayBuffer): MethodCall { + try { + const json = JSONMessageCodec.INSTANCE.decodeMessage(message); + if (ToolUtils.isObj(json)) { + const method = json["method"]; + const args = 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)); + } + } + + encodeSuccessEnvelope(result: any): ArrayBuffer { + return JSONMessageCodec.INSTANCE.encodeMessage([result]); + } + + encodeErrorEnvelope(errorCode: any, errorMessage: string, errorDetails: any) { + return JSONMessageCodec.INSTANCE.encodeMessage([errorCode, errorMessage, errorDetails]); + } + + encodeErrorEnvelopeWithStacktrace(errorCode: string, errorMessage: string, errorDetails: any, errorStacktrace: string): ArrayBuffer { + return JSONMessageCodec.INSTANCE.encodeMessage([errorCode, errorMessage, errorDetails, errorStacktrace]) + } + + decodeEnvelope(envelope: ArrayBuffer): any { + try { + const json = JSONMessageCodec.INSTANCE.decodeMessage(envelope); + if (json instanceof Array) { + if (json.length == 1) { + return json[0]; + } + if (json.length == 3) { + const code = json[0]; + const message = json[1]; + const details = 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MessageCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MessageCodec.ts new file mode 100644 index 0000000000..663d57af19 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MessageCodec.ts @@ -0,0 +1,30 @@ +/* +* 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. +*/ + +/** + * A message encoding/decoding mechanism. + */ +export default interface MessageCodec { + /** + * Encodes the specified message into binary. + */ + encodeMessage(message: T) : ArrayBuffer; + + /** + * Decodes the specified message from binary. + * + */ + decodeMessage(message: ArrayBuffer): T; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCall.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCall.ts new file mode 100644 index 0000000000..78fcd30def --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCall.ts @@ -0,0 +1,59 @@ +/* +* 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 ToolUtils from '../../util/ToolUtils'; +/** Command object representing a method call on a {@link MethodChannel}. */ +export default class MethodCall { + /** The name of the called method. */ + method: string; + + /** + * Arguments for the call. + * + *

Consider using {@link #arguments()} for cases where a particular run-time type is expected. + * Consider using {@link #argument(String)} when that run-time type is {@link Map} or {@link + * JSONObject}. + */ + args: any; + + constructor(method: string, args: any) { + this.method = method + this.args = args + } + + 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"); + } + } + + hasArgument(key: string): boolean { + if (arguments == null) { + return false; + } else if (arguments 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodChannel.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodChannel.ts new file mode 100644 index 0000000000..3f91fbc6cb --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodChannel.ts @@ -0,0 +1,214 @@ +/* +* 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 Log from '../../util/Log'; +import MessageChannelUtils from '../../util/MessageChannelUtils'; +import { BinaryMessageHandler, BinaryMessenger, BinaryReply, TaskQueue } from './BinaryMessenger'; +import MethodCall from './MethodCall'; +import MethodCodec from './MethodCodec'; +/** + * A named channel for communicating with the Flutter application using asynchronous method calls. + * + *

Incoming method calls are decoded from binary on receipt, and Java 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 Java type of method call arguments and results + * is {@code Object}, 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 { + static TAG = "MethodChannel#"; + private messenger: BinaryMessenger; + private name: string; + private codec: MethodCodec; + private taskQueue: TaskQueue; + + constructor(messenger: BinaryMessenger, name: string, codec?: MethodCodec, taskQueue?: TaskQueue) { + this.messenger = messenger + this.name = name + this.codec = codec + this.taskQueue = taskQueue + } + + /** + * 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 String of the method. + * @param arguments the arguments for the invocation, possibly null. + * @param callback a {@link Result} 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): 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 IncomingMethodCallHandler(handler, this.codec), this.taskQueue); + } else { + 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. + */ + 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 Result} 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 Result#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 (Android 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. See also Threading in + * the Flutter Engine. + * + * @param call A {@link MethodCall}. + * @param result A {@link Result} 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 String, 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; +} + +class IncomingResultHandler implements BinaryReply { + private callback: MethodResult; + private codec: MethodCodec; + + constructor(callback: MethodResult, codec: MethodCodec) { + this.callback = callback; + this.codec = codec + } + + reply(reply: ArrayBuffer): 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); + } + } +} + +class IncomingMethodCallHandler implements BinaryMessageHandler { + private handler: MethodCallHandler; + private codec: MethodCodec; + + constructor(handler: MethodCallHandler, codec: MethodCodec) { + this.handler = handler; + this.codec = codec + } + + 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 => { + 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)); + } + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCodec.ts new file mode 100644 index 0000000000..7370d83460 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/MethodCodec.ts @@ -0,0 +1,87 @@ +/* +* 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 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 String and an arguments Object. These data items are used to populate a {@link + * MethodCall}. + * + *

All operations throw {@link IllegalArgumentException}, if conversion fails. + */ +export default interface MethodCodec { + /** + * Encodes a message call into binary. + * + * @param methodCall a {@link MethodCall}. + * @return a {@link ByteBuffer} containing the encoding between position 0 and the current + * position. + */ + encodeMethodCall(methodCall: MethodCall): ArrayBuffer; + + /** + * Decodes a message call from binary. + * + * @param methodCall the binary encoding of the method call as a {@link ByteBuffer}. + * @return a {@link MethodCall} representation of the bytes between the given buffer's current + * position and its limit. + */ + decodeMethodCall(methodCall: ArrayBuffer): MethodCall; + + /** + * Encodes a successful result into a binary envelope message. + * + * @param result The result value, possibly null. + * @return a {@link ByteBuffer} containing the encoding between position 0 and the current + * position. + */ + 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 Throwable} in your + * codec. This is the most common value passed to this field. + * @return a {@link ByteBuffer} containing the encoding between position 0 and the current + * position. + */ + 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 Throwable} in your + * codec. This is the most common value passed to this field. + * @param errorStacktrace Platform stacktrace for the error. possibly null. + * @return a {@link ByteBuffer} containing the encoding between position 0 and the current + * position. + */ + 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 a {@link ByteBuffer}. + * @return the enveloped result Object. + * @throws FlutterException if the envelope was an error envelope. + */ + decodeEnvelope(envelope: ArrayBuffer): any +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/PluginRegistry.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/PluginRegistry.ts new file mode 100644 index 0000000000..cb92c8eb1a --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/PluginRegistry.ts @@ -0,0 +1,14 @@ +/* +* 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. +*/ diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMessageCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMessageCodec.ts new file mode 100644 index 0000000000..8dfb27153b --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMessageCodec.ts @@ -0,0 +1,310 @@ +/* +* 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 { ByteBuffer } from '../../util/ByteBuffer'; +import StringUtils from '../../util/StringUtils'; +import MessageCodec from './MessageCodec'; + +/** + * 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#"; + static INSTANCE = new StandardMessageCodec(); + + encodeMessage(message: any): ArrayBuffer { + if (message == null) { + return null; + } + const stream = ByteBuffer.from(new ArrayBuffer(1024)) + this.writeValue(stream, message); + return stream.buffer + } + + decodeMessage(message: ArrayBuffer): any { + 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; + + + 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) { + stream.writeInt8(StandardMessageCodec.INT32) + stream.writeInt32(value, true) + } else { + stream.writeInt8(StandardMessageCodec.INT64) + stream.writeInt64(value, true) + } + } else { //浮点型 + stream.writeInt8(StandardMessageCodec.FLOAT64) + this.writeAlignment(stream, 8); + stream.writeFloat64(value, true) + } + } 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 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) { + stream.writeInt8(StandardMessageCodec.LIST) + this.writeSize(stream, value.length); + value.forEach(item => this.writeValue(stream, item)) + } else if (value instanceof Map) { + stream.writeInt8(StandardMessageCodec.MAP) + this.writeSize(stream, value.size); + value.forEach((value, key) => { + this.writeValue(stream, key); + this.writeValue(stream, value); + }) + } else if (typeof value == 'object') { + this.writeValue(stream, new Map(Object.entries(value))) + } + return stream + } + + writeAlignment(stream: ByteBuffer, alignment: number) { + var mod = stream.byteOffset % alignment; + if (mod != 0) { + for (let i = 0; i < alignment - mod; i++) { + stream.writeInt8(0); + } + } + } + + writeSize(stream: ByteBuffer, value: number) { + if (value < 254) { + stream.writeInt8(value); + } else if (value <= 0xffff) { + stream.writeInt8(254); + stream.writeInt16(value, true); + } else { + stream.writeInt8(255); + stream.writeInt32(value, true); + } + } + + writeBytes(stream: ByteBuffer, bytes: Uint8Array) { + this.writeSize(stream, bytes.length) + bytes.forEach(item => stream.writeInt8(item)) + } + + readSize(buffer: ByteBuffer) { + let value = buffer.readInt8() & 0xff; + if (value < 254) { + return value; + } else if (value == 254) { + return buffer.readInt16(true); + } else { + return buffer.readInt32(true); + } + } + + readAlignment(buffer: ByteBuffer, alignment: number) { + let mod = buffer.byteOffset % alignment; + if (mod != 0) { + buffer.skip(alignment - mod); + } + } + + readValue(buffer: ByteBuffer): any { + let type = buffer.readInt8() + return this.readValueOfType(type, buffer); + } + + readBytes(buffer: ByteBuffer): Uint8Array { + let length = this.readSize(buffer); + let bytes = new Uint8Array(length) + for (let i = 0; i < length; i++) { + bytes[i] = buffer.readUint8() + } + return bytes; + } + + readValueOfType(type: number, buffer: ByteBuffer): any { + var result + 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); + break; + case StandardMessageCodec.BIGINT: + result = buffer.readBigInt64(true) + case StandardMessageCodec.FLOAT64: + this.readAlignment(buffer, 8); + result = buffer.readFloat64(true) + break; + case StandardMessageCodec.STRING: { + let bytes = this.readBytes(buffer); + result = StringUtils.arrayBufferToString(bytes.buffer); + 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: { //这里是都城array 还是 bigint待定 + let length = this.readSize(buffer); + let array = new Array(length) + this.readAlignment(buffer, 8); + for (let i = 0; i < length; i++) { + array[i] = buffer.readInt64(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 = 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 = 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"); + } + return result; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMethodCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMethodCodec.ts new file mode 100644 index 0000000000..4661aff7bb --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StandardMethodCodec.ts @@ -0,0 +1,116 @@ +/* +* 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 { ByteBuffer } from '../../util/ByteBuffer'; +import FlutterException from './FlutterException'; +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"; + public static INSTANCE = new StandardMethodCodec(StandardMessageCodec.INSTANCE); + + private messageCodec: StandardMessageCodec; + + /** Creates a new method codec based on the specified message codec. */ + constructor(messageCodec: StandardMessageCodec) { + this.messageCodec = messageCodec; + } + + 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; + } + + decodeMethodCall(methodCall: ArrayBuffer): MethodCall { + const buffer = ByteBuffer.from(methodCall); + const method = this.messageCodec.readValue(buffer); + const args = this.messageCodec.readValue(buffer); + if (typeof method == 'string' && !buffer.hasRemaining()) { + return new MethodCall(method, args); + } + throw new Error("Method call corrupted"); + } + + encodeSuccessEnvelope(result: any): ArrayBuffer { + const stream = ByteBuffer.from(new ArrayBuffer(1024)); + stream.writeInt8(0); + this.messageCodec.writeValue(stream, result); + return stream.buffer; + } + + 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; + } + + 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; + } + + decodeEnvelope(envelope: ArrayBuffer): any { + const buffer = ByteBuffer.from(envelope); + const flag = buffer.readInt8(); + switch (flag) { + case 0: { + const result = this.messageCodec.readValue(buffer); + if (!buffer.hasRemaining()) { + return result; + } + // Falls through intentionally. + } + case 1: { + const code = this.messageCodec.readValue(buffer); + const message = this.messageCodec.readValue(buffer); + const details = 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StringCodec.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StringCodec.ts new file mode 100644 index 0000000000..e43be76b55 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/common/StringCodec.ts @@ -0,0 +1,42 @@ +/* +* 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 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 { + static readonly INSTANCE = new StringCodec(); + + encodeMessage(message: string): ArrayBuffer { + if (message == null) { + return null; + } + return StringUtils.stringToArrayBuffer(message); + } + + decodeMessage(message: ArrayBuffer): string { + if (message == null) { + return null; + } + return StringUtils.arrayBufferToString(message); + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/ListenableEditingState.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/ListenableEditingState.ts new file mode 100644 index 0000000000..87f50bf489 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/ListenableEditingState.ts @@ -0,0 +1,266 @@ +/* +* 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 { 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'; + +const TAG = "ListenableEditingState"; +export class ListenableEditingState { + //Cache used to storage software keyboard input action + private mStringCache: string; + private mSelectionStartCache: number; + private mSelectionEndCache: number; + private mComposingStartCache: number; + private mComposingEndCache: number; + //used to compare with Cache + private mTextInputState: TextEditState; + private mListeners: ArrayList = new ArrayList(); + private mPendingListeners: ArrayList = new ArrayList(); + private mBatchTextEditingDeltas: ArrayList = new ArrayList(); + private mChangeNotificationDepth: number; + private mBatchEditNestDepth: number; + + private mTextWhenBeginBatchEdit: string; + private mSelectionStartWhenBeginBatchEdit: number; + private mSelectionEndWhenBeginBatchEdit: number; + private mComposingStartWhenBeginBatchEdit: number; + private mComposingEndWhenBeginBatchEdit: number; + + + constructor() { + this.mStringCache = ""; + this.mSelectionStartCache = 0; + this.mSelectionEndCache = 0; + this.mComposingStartCache = -1; + this.mComposingEndCache = -1; + } + + + getSelectionStart(): number { + return this.mSelectionStartCache; + } + + getSelectionEnd(): number { + return this.mSelectionEndCache; + } + + getComposingStart(): number { + return this.mComposingStartCache; + } + + getComposingEnd(): number { + return this.mComposingEndCache; + } + + getStringCache(): string { + return this.mStringCache; + } + + setSelectionStart(newSelectionStart: number): void { + this.mSelectionStartCache = newSelectionStart; + } + + setSelectionEnd(newSelectionEnd: number): void { + this.mSelectionEndCache = newSelectionEnd; + } + + setComposingStart(newComposingStart: number): void { + this.mComposingStartCache = newComposingStart; + } + + setComposingEnd(newComposingEnd: number): void { + this.mComposingEndCache = newComposingEnd; + } + + setStringCache(newStringCache: string): void { + this.mStringCache = newStringCache; + } + + notifyListener(listener: EditingStateWatcher, + textChanged: boolean, + selectionChanged: boolean, + composingChanged: boolean): void { + this.mChangeNotificationDepth++; + listener.didChangeEditingState(textChanged, selectionChanged, composingChanged); + this.mChangeNotificationDepth--; + } + + notifyListenersIfNeeded(textChanged: boolean, selectionChanged: boolean, composingChanged: boolean) { + if (textChanged || selectionChanged || composingChanged) { + for(const listener of this.mListeners) { + this.notifyListener(listener, textChanged, selectionChanged, composingChanged); + } + + } + } + + handleInsertTextEvent(text: string): void { + if(this.mTextInputState == null) { + Log.e(TAG, "mTextInputState is null"); + } + if(this.mStringCache.length == this.mSelectionStartCache) { + //Insert text one by one + this.mStringCache += text; + 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, this.mSelectionStartCache) + text + this.mStringCache.substring(this.mSelectionStartCache); + this.mStringCache = tempStr; + this.mSelectionStartCache += text.length; + this.mSelectionEndCache = this.mSelectionStartCache; + } + if(this.mListeners == null) { + Log.e(TAG, "mListeners is null"); + return; + } + this.notifyListenersIfNeeded(true, true, false); + } + + updateTextInputState(state: TextEditState): void { + this.beginBatchEdit(); + this.setStringCache(state.text); + if(state.hasSelection()) { + this.setSelectionStart(state.selectionStart); + this.setSelectionEnd(state.selectionEnd); + } else { + this.setSelectionStart(0); + this.setSelectionEnd(0); + } + //TODO: setComposingRange(state.composingStart,state.composingEnd); + this.endBatchEdit(); + } + + 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(); + } + } + + 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--; + + } + + 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); + } + } + + 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); + } + } + + handleDeleteEvent(leftOrRight: boolean, length: number): void { + if(leftOrRight == false) { + //delete left + if(this.mSelectionStartCache == 0) { + return; + } + this.mSelectionStartCache -= length; + let tempStr: string = this.mStringCache.slice(0, this.mSelectionStartCache) + this.mStringCache.slice(this.mSelectionStartCache + length); + this.mStringCache = tempStr; + this.mSelectionEndCache = this.mSelectionStartCache; + } else if(leftOrRight == true) { + //delete right + if(this.mSelectionStartCache == this.mStringCache.length) { + return; + } + this.mSelectionEndCache += length; + let tempStr: string = this.mStringCache.slice(0,this.mSelectionStartCache) + this.mStringCache.slice(this.mSelectionEndCache); + this.mStringCache = tempStr; + this.mSelectionStartCache = this.mSelectionEndCache; + } + this.notifyListenersIfNeeded(true, true, false); + } + + handleFunctionKey(functionKey: inputMethod.FunctionKey): void { + switch (functionKey.enterKeyType) { + case inputMethod.EnterKeyType.PREVIOUS: + case inputMethod.EnterKeyType.UNSPECIFIED: + case inputMethod.EnterKeyType.NONE: + case inputMethod.EnterKeyType.GO: + case inputMethod.EnterKeyType.SEARCH: + case inputMethod.EnterKeyType.SEND: + case inputMethod.EnterKeyType.NEXT: + case inputMethod.EnterKeyType.DONE: + + } + } + + handleSelectByRange(range: inputMethod.Range): void { + Log.d(TAG, "handleSelectByRange start: " + range.start +" end: " + range.end); + } + + + +} + +export interface EditingStateWatcher { + // Changing the editing state in a didChangeEditingState callback may cause unexpected + // behavior. + didChangeEditingState(textChanged: boolean, selectionChanged: boolean, composingRegionChanged: boolean); +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextEditingDelta.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextEditingDelta.ts new file mode 100644 index 0000000000..8b8064fbee --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextEditingDelta.ts @@ -0,0 +1,79 @@ +/* +* 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 Log from '../../util/Log'; + +export class TextEditingDelta { + private static TAG = "TextEditingDelta"; + private oldText: string; + private deltaText: string; + private deltaStart: number; + private deltaEnd: number; + private newSelectionStart: number; + private newSelectionEnd: number; + private newComposingStart: number; + private newComposingEnd: number; + + 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); + } + + } + + setDeltas(oldText: string, newText: string, newStart: number, newExtent: number): void { + this.oldText = oldText; + this.deltaText = newText; + this.deltaStart = newStart; + this.deltaEnd = newExtent; + } + + toJSON(): any { + let delta; + try { + delta.oldText = this.oldText; + delta.deltaText = this.deltaText; + delta.deltaStart = this.deltaStart; + delta.deltaEnd = this.deltaEnd; + delta.selectionBase = this.newSelectionStart; + delta.selectionExtent = this.newSelectionEnd; + delta.composingBase = this.newComposingStart; + delta.composingExtent = this.newComposingEnd; + } catch (err) { + Log.e(TextEditingDelta.TAG,"unable to create JSONObject: " + JSON.stringify(err)); + } + + + } +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextInputPlugin.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextInputPlugin.ts new file mode 100644 index 0000000000..a8441dffa5 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/editing/TextInputPlugin.ts @@ -0,0 +1,250 @@ +/* +* 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 TextInputChannel, { Configuration, TextEditState, + TextInputType } from '../../embedding/engine/systemchannels/TextInputChannel'; +import inputMethod from '@ohos.inputMethod'; +import Log from '../../util/Log'; +import { EditingStateWatcher, ListenableEditingState } from './ListenableEditingState'; +import { TextEditingDelta } from './TextEditingDelta'; + +export default class TextInputPlugin implements EditingStateWatcher{ + private static TAG = "TextInputPlugin"; + private textInputChannel: TextInputChannel; + private textConfig: inputMethod.TextConfig; + private inputMethodController: inputMethod.InputMethodController; + private inputTarget: InputTarget; + private configuration: Configuration; + //TODO:private autofillConfiguration: + private mEditable: ListenableEditingState; + private mRestartInputPending: boolean; + + private imcFlag: boolean = false; + + + + constructor(textInputChannel: TextInputChannel) { + this.textInputChannel = textInputChannel; + this.mEditable = new ListenableEditingState(); + this.textConfig = { + inputAttribute: { + textInputType: 0, + enterKeyType: 1 + }}; + this.inputMethodController = inputMethod.getController(); + this.textInputChannel.setTextInputMethodHandler({ + show:(): void => { + this.showTextInput(); + }, + + hide:(): void => { + this.hideTextInput(); + }, + + requestAutofill:(): void =>{ + + }, + + finishAutofillContext:(shouldSave: boolean): void =>{ + + }, + + setClient:(textInputClientId: number, configuration: Configuration): void => { + Log.d(TextInputPlugin.TAG,"textInputClientId: " + textInputClientId); + this.setTextInputClient(textInputClientId, configuration); + }, + + setPlatformViewClient:(id: number, usesVirtualDisplay: boolean): void => { + + }, + + setEditableSizeAndTransform:(width: number, height: number, transform: number[]): void => { + + }, + + setEditingState:(editingState: TextEditState): void => { + Log.d(TextInputPlugin.TAG, "text:" + editingState.text +" selectionStart:" + editingState.selectionStart + " selectionEnd:" + + editingState.selectionEnd + " composingStart:" + editingState.composingStart + " composingEnd" + editingState.composingEnd); + this.mEditable.updateTextInputState(editingState); + }, + clearClient:(): void =>{ + this.clearTextInputClient(); + } + + + }); + } + + private async showTextInput(): Promise { + await this.attach(true); + if(this.imcFlag != true) { + this.listenKeyBoardEvent(); + } + this.inputMethodController.showSoftKeyboard().then(()=> { + Log.d(TextInputPlugin.TAG, "Succeeded in showing softKeyboard"); + }).catch((err) => { + Log.e(TextInputPlugin.TAG, "Failed to show softKeyboard:" + JSON.stringify(err)); + }); + } + + private async hideTextInput(): Promise { + this.inputMethodController.hideSoftKeyboard().then(() => { + Log.d(TextInputPlugin.TAG, "Succeeded in hide softKeyboard"); + }).catch((err) => { + Log.e(TextInputPlugin.TAG, "Failed to hide softKeyboard:" + JSON.stringify(err)); + }) + } + + async attach(showKeyboard: boolean): Promise { + try { + await this.inputMethodController.attach(showKeyboard, this.textConfig); + } catch (err) { + Log.e(TextInputPlugin.TAG, "Failed to attach:" + JSON.stringify(err)); + } + } + + listenKeyBoardEvent(): void { + try { + this.inputMethodController.on('insertText', (text) => { + Log.d(TextInputPlugin.TAG, "insertText: " + text); + this.mEditable.handleInsertTextEvent(text); + }); + } catch (err) { + Log.e(TextInputPlugin.TAG, "Failed to subscribe insertText:" + JSON.stringify(err)); + this.cancelListenKeyBoardEvent(); + return; + } + + try { + this.inputMethodController.on('deleteLeft', (length) => { + this.mEditable.handleDeleteEvent(false, length); + }) + } catch (err) { + Log.e(TextInputPlugin.TAG, "Failed to subscribe deleteLeft:" + JSON.stringify(err)); + this.cancelListenKeyBoardEvent(); + return; + } + + try { + this.inputMethodController.on('deleteRight', (length) => { + this.mEditable.handleDeleteEvent(true, length); + }) + } catch (err) { + Log.e(TextInputPlugin.TAG, "Failed to subscribe deleteRight:" + JSON.stringify(err)); + this.cancelListenKeyBoardEvent(); + return; + } + + try { + this.inputMethodController.on('sendFunctionKey', (functionKey) => { + this.mEditable.handleFunctionKey(functionKey); + }) + } catch (err) { + Log.e(TextInputPlugin.TAG, "Failed to subscribe sendFunctionKey:" + JSON.stringify(err)); + this.cancelListenKeyBoardEvent(); + return; + } + + try { + this.inputMethodController.on('selectByRange', (range) => { + this.mEditable.handleSelectByRange(range); + }) + } catch (err) { + Log.e(TextInputPlugin.TAG, "Failed to subscribe selectByRange:" + JSON.stringify(err)); + this.cancelListenKeyBoardEvent(); + return; + } + Log.d(TextInputPlugin.TAG, "listenKeyBoardEvent success"); + this.imcFlag = true; + } + + cancelListenKeyBoardEvent(): void { + this.inputMethodController.off('insertText'); + this.inputMethodController.off('deleteLeft'); + this.inputMethodController.off('deleteRight'); + this.inputMethodController.off('sendFunctionKey'); + this.inputMethodController.off('selectByRange'); + } + + setTextInputClient(client: number, configuration: Configuration): void { + this.configuration = configuration; + if(this.canShowTextInput()) { + this.inputTarget = new InputTarget(Type.FRAMEWORK_CLIENT, client); + } else { + this.inputTarget = new InputTarget(Type.NO_TARGET, client); + } + this.mEditable.removeEditingStateListener(this); + this.mEditable = new ListenableEditingState(); + + this.mRestartInputPending = true; + this.mEditable.addEditingStateListener(this); + } + + canShowTextInput(): boolean { + if(this.configuration == null || this.configuration.inputType == null) { + return true; + } + return this.configuration.inputType.type != TextInputType.NONE; + } + + setTextInputEditingState(state: TextEditState) { + + } + + didChangeEditingState(textChanged: boolean, selectionChanged: boolean, composingRegionChanged: boolean): void { + this.textInputChannel.updateEditingState(this.inputTarget.id, this.mEditable.getStringCache(), + this.mEditable.getSelectionStart(), this.mEditable.getSelectionEnd(), + this.mEditable.getComposingStart(), this.mEditable.getComposingEnd()) + } + + detach(): void { + this.inputMethodController.detach((err) => { + if(err) { + Log.e(TextInputPlugin.TAG, "Failed to detach: " + JSON.stringify(err)); + } + }) + } + + clearTextInputClient(): void { + if(this.inputTarget.type == Type.VIRTUAL_DISPLAY_PLATFORM_VIEW) { + return; + } + this.mEditable.removeEditingStateListener(this); + this.configuration = null; + this.inputTarget = new InputTarget(Type.NO_TARGET, 0); + } + +} + +enum Type { + 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, + PHYSICAL_DISPLAY_PLATFORM_VIEW, +} + +export class InputTarget { + type: Type; + id: number; + + constructor(type: Type, id: number) { + this.type = type; + this.id = id; + } + +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/localization/LocalizationPlugin.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/localization/LocalizationPlugin.ts new file mode 100644 index 0000000000..662e087641 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/localization/LocalizationPlugin.ts @@ -0,0 +1,63 @@ +/* +* 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 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"; +export default class LocalizationPlugin { + private localizationChannel:LocalizationChannel; + private context: common.Context; + + 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 = { + getStringResource(key: string, localeString: string): string { + Log.i(TAG, "getResource enter"); + return "" + } + } + constructor(context: common.Context, localizationChannel: LocalizationChannel) { + this.context = context; + this.localizationChannel = localizationChannel; + this.localizationChannel.setLocalizationMessageHandler(this.localizationMessageHandler); + } + + sendLocaleToFlutter(): void { + let systemLanguages = i18n.System.getSystemLanguages(); + this.localizationChannel.sendLocales(systemLanguages); + } +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/mouse/MouseCursorPlugin.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/mouse/MouseCursorPlugin.ts new file mode 100644 index 0000000000..4aa609f9cf --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/plugin/mouse/MouseCursorPlugin.ts @@ -0,0 +1,129 @@ +/* +* 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 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 { AsyncCallback } from '@ohos.base'; + +const TAG: string = "MouseCursorPlugin"; +export default class MouseCursorPlugin implements MouseCursorMethodHandler{ + private mView: MouseCursorViewDelegate; + + private mouseCursorChannel: MouseCursorChannel; + + private systemCursorConstants: HashMap; + + constructor(mouseCursorView: MouseCursorViewDelegate, mouseCursorChannel: MouseCursorChannel) { + this.mView = mouseCursorView; + this.mouseCursorChannel = mouseCursorChannel; + this.mouseCursorChannel.setMethodHandler(this); + } + + activateSystemCursor(kind: string): void { + this.mView.getWindowId((error, windowId) => { + if (windowId < 0) { + Log.w(TAG, "set point style failed windowId is invalid"); + return; + } + let pointStyle: pointer.PointerStyle = this.resolveSystemCursor(kind); + try { + pointer.setPointerStyle(windowId, pointStyle, (err) => { + Log.i(TAG, "set point style success kind : " + kind); + }) + } catch (e) { + Log.e(TAG, "set point style failed : " + kind + " " + JSON.stringify(e)); + } + }); + } + + /** + * Return mouse cursor point style + * + *

This method guarantees to return a non-null object. + * + * @param kind mouse cursor type + * @returns point style + */ + 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("resizeUpLeft", pointer.PointerStyle.NORTH_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.MOVE); + this.systemCursorConstants.set("resizeUpRightDownLeft", pointer.PointerStyle.MOVE); + 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); + } + let pointStyle:pointer.PointerStyle = this.systemCursorConstants.get(kind); + if (pointStyle === null) { + return pointer.PointerStyle.DEFAULT; + } + return pointStyle; + } + + /** + * Detaches the text input plugin from the platform views controller; + * + *

The MouseCursorPlugin instance should not be used after call this. + */ + destroy(): void { + this.mouseCursorChannel.setMethodHandler(null); + } +} + +/** + * Delegate interface for requesting the system to display a pointer icon object. + * + *

Typically implemented by an component, such as a{@code FlutterView} + */ +export interface MouseCursorViewDelegate { + /** + * get window id to set mouse style + *

component need to implement this interface to get windowId + * + * @param callback windowId + * */ + getWindowId(callback: AsyncCallback): void; +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ByteBuffer.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ByteBuffer.ts new file mode 100644 index 0000000000..2a809ad854 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ByteBuffer.ts @@ -0,0 +1,814 @@ +/* +* 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 util from '@ohos.util' + +/** + * 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 { + if (ArrayBuffer.isView(source)) { + byteOffset = source.byteOffset + (byteOffset || 0) + } + const byteBuffer = new ByteBuffer() + byteBuffer.dataView = byteLength === undefined ? new DataView(source, byteOffset) : new DataView(source, byteOffset, Math.min(source.byteLength, byteLength)) + byteBuffer.#byteOffset = byteBuffer.dataView.byteOffset + return byteBuffer + } + + /** + * The dataView. + */ + private dataView: DataView + + /** + * The byte offset. + */ + #byteOffset: number = 0 + + /** + * The byte offset. + * @returns The byte offset. + */ + get byteOffset(): number { + return this.#byteOffset + } + + /** + * The byte offset. + * @returns The byte offset. + */ + get byteLength(): number { + return this.dataView.byteLength + } + + /** + * The number of remaining bytes. + * @returns The number of bytes remaining. + */ + get bytesRemaining(): number { + return this.dataView.byteLength - this.#byteOffset + } + + hasRemaining(): boolean { + return this.#byteOffset < this.dataView.byteLength; + } + + get buffer(): ArrayBuffer { + const dataBuffer = new DataView(new ArrayBuffer(this.#byteOffset)); + for (var i = 0; i < this.#byteOffset; i++) { + dataBuffer.setUint8(i, this.dataView.getUint8(i)); + } + return dataBuffer.buffer + } + + /** + * Skips the byte offset. + * @param byteLength The byte length. + */ + skip(byteLength: number): void { + this.#byteOffset += byteLength + } + + /** + * Resets the byte offset. + */ + reset(): void { + this.#byteOffset = this.dataView.byteOffset + } + + /** + * Clears the byte buffer. + */ + clear(): void { + this.getUint8Array(0).fill(0) + } + + /** + * check buffer capacity. + */ + checkWriteCapacity(slen: number): void { + if (this.#byteOffset + slen > this.dataView.byteLength) { + var checkBuffer = new DataView(new ArrayBuffer(this.dataView.byteLength + slen + 512)); + for (var i = 0; i < this.#byteOffset; i++) { + checkBuffer.setUint8(i, this.dataView.getUint8(i)); + } + this.dataView = checkBuffer; + } + } + + /** + * 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.#byteOffset++) !== 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.#byteOffset++, value ? 1 : 0) + } + + /** + * Gets an signed byte. + * @param byteOffset The byte offset. + * @returns The value. + */ + getInt8(byteOffset: number): number { + return this.dataView.getInt8(byteOffset) + } + + /** + * Reads the next signed byte. + * @returns The value. + */ + readInt8(): number { + return this.getInt8(this.#byteOffset++) + } + + /** + * 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.#byteOffset++, value) + } + + /** + * Gets an unsigned byte. + * @param byteOffset The byte offset. + * @returns The value. + */ + getUint8(byteOffset: number): number { + return this.dataView.getUint8(byteOffset) + } + + /** + * Reads the next unsigned byte. + * @returns The value. + */ + readUint8(): number { + return this.getUint8(this.#byteOffset++) + } + + /** + * 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.#byteOffset++, value) + } + + /** + * Gets an signed short. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getInt16(byteOffset: number, littleEndian?: boolean): number { + return this.dataView.getInt16(byteOffset, littleEndian) + } + + /** + * Reads the next signed short. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readInt16(littleEndian?: boolean): number { + const value = this.getInt16(this.#byteOffset, littleEndian) + this.#byteOffset += 2 + return value + } + + /** + * Sets a signed short. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeInt16(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(2) + this.setInt16(this.#byteOffset, value, littleEndian) + this.#byteOffset += 2 + } + + /** + * Gets an unsigned short. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getUint16(byteOffset: number, littleEndian?: boolean): number { + return this.dataView.getUint16(byteOffset, littleEndian) + } + + /** + * Reads the next unsigned short. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readUint16(littleEndian?: boolean): number { + const value = this.getUint16(this.#byteOffset, littleEndian) + this.#byteOffset += 2 + return value + } + + /** + * Sets an unsigned short. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeUint16(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(2) + this.setUint16(this.#byteOffset, value, littleEndian) + this.#byteOffset += 2 + } + + /** + * Gets an signed integer. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getInt32(byteOffset: number, littleEndian?: boolean): number { + return this.dataView.getInt32(byteOffset, littleEndian) + } + + /** + * Reads the next signed integer. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readInt32(littleEndian?: boolean): number { + const value = this.getInt32(this.#byteOffset, littleEndian) + this.#byteOffset += 4 + return value + } + + /** + * Sets a signed integer. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeInt32(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(4) + this.setInt32(this.#byteOffset, value, littleEndian) + this.#byteOffset += 4 + } + + /** + * Gets an unsigned integer. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getUint32(byteOffset: number, littleEndian?: boolean): number { + return this.dataView.getUint32(byteOffset, littleEndian) + } + + /** + * Reads the next unsigned integer. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readUint32(littleEndian?: boolean): number { + const value = this.getUint32(this.#byteOffset, littleEndian) + this.#byteOffset += 4 + return value + } + + /** + * Sets an unsigned integer. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeUint32(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(4) + this.setUint32(this.#byteOffset, value, littleEndian) + this.#byteOffset += 4 + } + + /** + * Gets a float. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getFloat32(byteOffset: number, littleEndian?: boolean): number { + return this.dataView.getFloat32(byteOffset, littleEndian) + } + + /** + * Reads the next float. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readFloat32(littleEndian?: boolean): number { + const value = this.getFloat32(this.#byteOffset, littleEndian) + this.#byteOffset += 4 + return value + } + + /** + * Sets a float. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeFloat32(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(4) + this.setFloat32(this.#byteOffset, value, littleEndian) + this.#byteOffset += 4 + } + + /** + * Gets a double. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getFloat64(byteOffset: number, littleEndian?: boolean): number { + return this.dataView.getFloat64(byteOffset, littleEndian) + } + + /** + * Reads the next double. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readFloat64(littleEndian?: boolean): number { + const value = this.getFloat64(this.#byteOffset, littleEndian) + this.#byteOffset += 8 + return value + } + + /** + * Sets a double. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeFloat64(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(8) + this.setFloat64(this.#byteOffset, value, littleEndian) + this.#byteOffset += 8 + } + + /** + * Gets an signed long. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getBigInt64(byteOffset: number, littleEndian?: boolean): bigint { + return this.dataView.getBigInt64(byteOffset, littleEndian) + } + + /** + * Reads the next signed long. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readBigInt64(littleEndian?: boolean): bigint { + const value = this.getBigInt64(this.#byteOffset, littleEndian) + this.#byteOffset += 8 + return value + } + + /** + * Sets a signed long. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeBigInt64(value: bigint, littleEndian?: boolean): void { + this.checkWriteCapacity(8) + this.setBigInt64(this.#byteOffset, value, littleEndian) + this.#byteOffset += 8 + } + + /** + * Gets an unsigned long. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getBigUint64(byteOffset: number, littleEndian?: boolean): bigint { + return this.dataView.getBigUint64(byteOffset, littleEndian) + } + + /** + * Reads the next unsigned long. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readBigUint64(littleEndian?: boolean): bigint { + const value = this.getBigUint64(this.#byteOffset, littleEndian) + this.#byteOffset += 8 + return value + } + + /** + * Sets an unsigned long. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeBigUint64(value: bigint, littleEndian?: boolean): void { + this.checkWriteCapacity(8) + this.setBigUint64(this.#byteOffset, value, littleEndian) + this.#byteOffset += 8 + } + + /** + * Gets an signed long. + * @param byteOffset The byte offset. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + getInt64(byteOffset: number, littleEndian?: boolean): number { + return Number(this.getBigInt64(byteOffset, littleEndian)) + } + + /** + * Reads the next signed long. + * @param littleEndian If the value is little endian. + * @returns The value. + */ + readInt64(littleEndian?: boolean): number { + const value = this.getInt64(this.#byteOffset, littleEndian) + this.#byteOffset += 8 + return value + } + + /** + * Sets a signed long. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeInt64(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(8) + this.setInt64(this.#byteOffset, value, littleEndian) + this.#byteOffset += 8 + } + + /** + * Gets an unsigned long. + * @param byteOffset The byte offset. + * @param littleEndian If 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 If the value is little endian. + * @returns The value. + */ + readUint64(littleEndian?: boolean): number { + const value = this.getUint64(this.#byteOffset, littleEndian) + this.#byteOffset += 8 + return value + } + + /** + * Sets an unsigned long. + * @param byteOffset The byte offset. + * @param value The value. + * @param littleEndian If 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 If the value is little endian. + */ + writeUint64(value: number, littleEndian?: boolean): void { + this.checkWriteCapacity(8) + this.setUint64(this.#byteOffset, value, littleEndian) + this.#byteOffset += 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 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.#byteOffset, byteLength) + this.#byteOffset += 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.#byteOffset, value) + this.#byteOffset += 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 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.#byteOffset, byteLength) + this.#byteOffset += 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.#byteOffset, value) + this.#byteOffset += 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.#byteOffset, byteLength, byteEncoding) + if (byteLength === undefined) { + this.#byteOffset = this.dataView.byteLength + } else { + this.#byteOffset += byteLength + } + return value + } + + /** + * Sets a string. + * @param byteOffset The byte offset. + * @param value The string. + * @param byteEncoding The byte encoding. + * @returns The byte length. + */ + setString(byteOffset: number, value: string, byteEncoding?: string, write?: boolean): number { + // TODO: add support for "utf-16-be" and "utf-16-le" + 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) + return written || 0 + } + + /** + * Writes the next a string. + * @param value The string. + * @param byteEncoding The byte encoding. + */ + writeString(value: string, byteEncoding?: string): void { + const byteLength = this.setString(this.#byteOffset, value, byteEncoding, true) + this.#byteOffset += byteLength + } + + /** + * Formats to a string. + * @param format The string format. + * @returns The string. + */ + toString(format?: string): string { + return Array.prototype.map.call(this.getUint8Array(0), function(byte: number): string { + 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/Log.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/Log.ts new file mode 100644 index 0000000000..cdec09bb74 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/Log.ts @@ -0,0 +1,122 @@ +/* +* 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 HiLog from '@ohos.hilog'; + +const DOMAIN: number = 0x00FF; +const TAG = "Flutter"; +const SYMBOL = " --> "; +const FILTER_KEYS = [ + new RegExp('hide', "gi") +] + +export function filterKey(target: any, propKey: string, descriptor: PropertyDescriptor) { + const original = descriptor.value; + descriptor.value = function (...args: string[]) { + let filterResult = args.map((str) => { + let tempStr = str + FILTER_KEYS.forEach((filterKey) => tempStr = tempStr.replace(filterKey, "**")) + return tempStr + }); + const result = original.call(this, ...filterResult); + return result; + }; +} + +/** + * Basic log class + */ +export default class Log { + /** + * Outputs debug-level logs. + * + * @param tag Identifies the log tag. + * @param format Indicates the log format string. + * @param args Indicates the log parameters. + * @since 7 + */ + static d(tag: string, format: string, ...args: any[]) { + if (Log.isLoggable(HiLog.LogLevel.DEBUG)) { + HiLog.debug(DOMAIN, TAG, tag + SYMBOL + format, args); + } + } + + /** + * Outputs info-level logs. + * + * @param tag Identifies the log tag. + * @param format Indicates the log format string. + * @param args Indicates the log parameters. + * @since 7 + */ + static i(tag: string, format: string, ...args: any[]) { + if (Log.isLoggable(HiLog.LogLevel.INFO)) { + HiLog.info(DOMAIN, TAG, tag + SYMBOL + format, args); + } + } + + /** + * Outputs warning-level logs. + * + * @param tag Identifies the log tag. + * @param format Indicates the log format string. + * @param args Indicates the log parameters. + * @since 7 + */ + static w(tag: string, format: string, ...args: any[]) { + if (Log.isLoggable(HiLog.LogLevel.WARN)) { + HiLog.warn(DOMAIN, TAG, tag + SYMBOL + format, args); + } + } + + /** + * Outputs error-level logs. + * + * @param tag Identifies the log tag. + * @param format Indicates the log format string. + * @param args Indicates the log parameters. + * @since 7 + */ + static e(tag: string, format: string, ...args: any[]) { + if (Log.isLoggable(HiLog.LogLevel.ERROR)) { + HiLog.error(DOMAIN, TAG, tag + SYMBOL + format, args); + } + } + + /** + * Outputs fatal-level logs. + * + * @param tag Identifies the log tag. + * @param format Indicates the log format string. + * @param args Indicates the log parameters. + * @since 7 + */ + static f(tag: string, format: string, ...args: any[]) { + 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 tag Identifies the log tag. + * @param level log level + * @since 7 + */ + private static isLoggable(level: HiLog.LogLevel): boolean { + return HiLog.isLoggable(DOMAIN, TAG, level); + } +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/MessageChannelUtils.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/MessageChannelUtils.ts new file mode 100644 index 0000000000..1899f74069 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/MessageChannelUtils.ts @@ -0,0 +1,25 @@ +/* +* 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 BasicMessageChannel from '../plugin/common/BasicMessageChannel'; +import { BinaryMessenger } from '../plugin/common/BinaryMessenger'; +import StringUtils from './StringUtils'; + +export default class MessageChannelUtils { + 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/PathUtils.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/PathUtils.ts new file mode 100644 index 0000000000..239bc06e76 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/PathUtils.ts @@ -0,0 +1,31 @@ +/* +* 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. +*/ + +/** + * ohos路径获取工具 + */ +export default class PathUtils { + static getFilesDir(): string { + return null; + } + + static getCacheDirectory(): string { + return null; + } + + static getDataDirectory(): string { + return null; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/StringUtils.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/StringUtils.ts new file mode 100644 index 0000000000..aeb7c554c3 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/StringUtils.ts @@ -0,0 +1,39 @@ +/* +* 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 util from '@ohos.util' + +/** + * 默认字符串工具 + */ +export default class StringUtils { + static stringToArrayBuffer(str: string): ArrayBuffer { + let textEncoder = new util.TextEncoder("utf-8"); + return textEncoder.encodeInto(str).buffer; + } + + static arrayBufferToString(buffer: ArrayBuffer): string { + let textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM : true }) + return textDecoder.decode(new Uint8Array(buffer)) + } + + static isNotEmpty(str: string): boolean { + return str && str.length > 0; + } + + static isEmpty(str: string): boolean { + return (!str) || str.length == 0; + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ToolUtils.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ToolUtils.ts new file mode 100644 index 0000000000..c7b5f9dc2f --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/ToolUtils.ts @@ -0,0 +1,24 @@ +/* +* 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. +*/ + +export default class ToolUtils { + static isObj(object: any): boolean { + return object && typeof (object) == 'object' && Object.prototype.toString.call(object).toLowerCase() == "[object object]"; + } + + 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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/TraceSection.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/TraceSection.ts new file mode 100644 index 0000000000..7560012dc3 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/util/TraceSection.ts @@ -0,0 +1,39 @@ +/* +* 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 hiTraceMeter from '@ohos.hiTraceMeter' + +export class TraceSection { + + static taskId: number = 1; + + 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. + */ + public static begin(sectionName: string): void { + hiTraceMeter.startTrace(this.cropSectionName(sectionName), this.taskId++); + } + + /** Wraps Trace.endSection. */ + public static end(sectionName: string): void { + hiTraceMeter.finishTrace(this.cropSectionName(sectionName), this.taskId); + } +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ts new file mode 100644 index 0000000000..0e08f41fe9 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/AccessibilityBridge.ts @@ -0,0 +1,45 @@ +/* +* 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. +*/ + +export default class AccessibilityBridge { + constructor(){ + + } +} + +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/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterCallbackInformation.ts b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterCallbackInformation.ts new file mode 100644 index 0000000000..f52152001a --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/ets/view/FlutterCallbackInformation.ts @@ -0,0 +1,39 @@ +/* +* 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 FlutterNapi from '../embedding/engine/FlutterNapi'; + +export class FlutterCallbackInformation { + callbackName: string; + callbackClassName: string; + callbackLibraryPath: string; + + /** + * Get callback information for a given handle. + * + * @param handle the handle for the callback, generated by `PluginUtilities.getCallbackHandle` in + * `dart:ui`. + * @return an instance of FlutterCallbackInformation for the provided handle. + */ + static lookupCallbackInformation(handle: number): FlutterCallbackInformation { + return FlutterNapi.nativeLookupCallbackInformation(handle); + } + + constructor(callbackName: string, callbackClassName: string, callbackLibraryPath: string) { + this.callbackName = callbackName; + this.callbackClassName = callbackClassName; + this.callbackLibraryPath = callbackLibraryPath; + } +} diff --git a/shell/platform/ohos/flutter_embedding/flutter/src/main/module.json5 b/shell/platform/ohos/flutter_embedding/flutter/src/main/module.json5 new file mode 100644 index 0000000000..61db0f5cae --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/flutter/src/main/module.json5 @@ -0,0 +1,24 @@ +/* +* 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. +*/ + +{ + "module": { + "name": "flutter", + "type": "har", + "deviceTypes": [ + "default" + ] + } +} diff --git a/shell/platform/ohos/flutter_embedding/hvigor/hvigor-config.json5 b/shell/platform/ohos/flutter_embedding/hvigor/hvigor-config.json5 new file mode 100755 index 0000000000..d550d9bb1c --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/hvigor/hvigor-config.json5 @@ -0,0 +1,21 @@ +/* +* 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. +*/ + +{ + "hvigorVersion": "2.4.2", + "dependencies": { + "@ohos/hvigor-ohos-plugin": "2.4.2" + } +} diff --git a/shell/platform/ohos/flutter_embedding/hvigor/hvigor-wrapper.js b/shell/platform/ohos/flutter_embedding/hvigor/hvigor-wrapper.js new file mode 100755 index 0000000000..994f22987b --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/hvigor/hvigor-wrapper.js @@ -0,0 +1,2 @@ +"use strict";var e=require("fs"),t=require("path"),n=require("os"),r=require("crypto"),u=require("child_process"),o=require("constants"),i=require("stream"),s=require("util"),c=require("assert"),a=require("tty"),l=require("zlib"),f=require("net");function d(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var D=d(e),p=d(t),E=d(n),m=d(r),h=d(u),y=d(o),C=d(i),F=d(s),g=d(c),A=d(a),v=d(l),S=d(f),w="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},O={},b={},_={},B=w&&w.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(_,"__esModule",{value:!0}),_.isMac=_.isLinux=_.isWindows=void 0;const P=B(E.default),k="Windows_NT",x="Linux",N="Darwin";_.isWindows=function(){return P.default.type()===k},_.isLinux=function(){return P.default.type()===x},_.isMac=function(){return P.default.type()===N};var I={},T=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),R=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),M=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&T(t,e,n);return R(t,e),t};Object.defineProperty(I,"__esModule",{value:!0}),I.hash=void 0;const L=M(m.default);I.hash=function(e,t="md5"){return L.createHash(t).update(e,"utf-8").digest("hex")},function(e){var t=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),n=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),r=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var r={};if(null!=e)for(var u in e)"default"!==u&&Object.prototype.hasOwnProperty.call(e,u)&&t(r,e,u);return n(r,e),r};Object.defineProperty(e,"__esModule",{value:!0}),e.HVIGOR_BOOT_JS_FILE_PATH=e.HVIGOR_PROJECT_DEPENDENCY_PACKAGE_JSON_PATH=e.HVIGOR_PROJECT_DEPENDENCIES_HOME=e.HVIGOR_PROJECT_WRAPPER_HOME=e.HVIGOR_PROJECT_NAME=e.HVIGOR_PROJECT_ROOT_DIR=e.HVIGOR_PROJECT_CACHES_HOME=e.HVIGOR_PNPM_STORE_PATH=e.HVIGOR_WRAPPER_PNPM_SCRIPT_PATH=e.HVIGOR_WRAPPER_TOOLS_HOME=e.HVIGOR_USER_HOME=e.DEFAULT_PACKAGE_JSON=e.DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME=e.PNPM=e.HVIGOR=e.NPM_TOOL=e.PNPM_TOOL=e.HVIGOR_ENGINE_PACKAGE_NAME=void 0;const u=r(p.default),o=r(E.default),i=_,s=I;e.HVIGOR_ENGINE_PACKAGE_NAME="@ohos/hvigor",e.PNPM_TOOL=(0,i.isWindows)()?"pnpm.cmd":"pnpm",e.NPM_TOOL=(0,i.isWindows)()?"npm.cmd":"npm",e.HVIGOR="hvigor",e.PNPM="pnpm",e.DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME="hvigor-config.json5",e.DEFAULT_PACKAGE_JSON="package.json",e.HVIGOR_USER_HOME=u.resolve(o.homedir(),".hvigor"),e.HVIGOR_WRAPPER_TOOLS_HOME=u.resolve(e.HVIGOR_USER_HOME,"wrapper","tools"),e.HVIGOR_WRAPPER_PNPM_SCRIPT_PATH=u.resolve(e.HVIGOR_WRAPPER_TOOLS_HOME,"node_modules",".bin",e.PNPM_TOOL),e.HVIGOR_PNPM_STORE_PATH=u.resolve(e.HVIGOR_USER_HOME,"caches"),e.HVIGOR_PROJECT_CACHES_HOME=u.resolve(e.HVIGOR_USER_HOME,"project_caches"),e.HVIGOR_PROJECT_ROOT_DIR=process.cwd(),e.HVIGOR_PROJECT_NAME=u.basename((0,s.hash)(e.HVIGOR_PROJECT_ROOT_DIR)),e.HVIGOR_PROJECT_WRAPPER_HOME=u.resolve(e.HVIGOR_PROJECT_ROOT_DIR,e.HVIGOR),e.HVIGOR_PROJECT_DEPENDENCIES_HOME=u.resolve(e.HVIGOR_PROJECT_CACHES_HOME,e.HVIGOR_PROJECT_NAME,"workspace"),e.HVIGOR_PROJECT_DEPENDENCY_PACKAGE_JSON_PATH=u.resolve(e.HVIGOR_PROJECT_DEPENDENCIES_HOME,e.DEFAULT_PACKAGE_JSON),e.HVIGOR_BOOT_JS_FILE_PATH=u.resolve(e.HVIGOR_PROJECT_DEPENDENCIES_HOME,"node_modules","@ohos","hvigor","bin","hvigor.js")}(b);var j={},$={};Object.defineProperty($,"__esModule",{value:!0}),$.logInfoPrintConsole=$.logErrorAndExit=void 0,$.logErrorAndExit=function(e){e instanceof Error?console.error(e.message):console.error(e),process.exit(-1)},$.logInfoPrintConsole=function(e){console.log(e)};var H=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),J=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),G=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&H(t,e,n);return J(t,e),t},V=w&&w.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(j,"__esModule",{value:!0}),j.isFileExists=j.offlinePluginConversion=j.executeCommand=j.getNpmPath=j.hasNpmPackInPaths=void 0;const U=h.default,W=G(p.default),z=b,K=$,q=V(D.default);j.hasNpmPackInPaths=function(e,t){try{return require.resolve(e,{paths:[...t]}),!0}catch(e){return!1}},j.getNpmPath=function(){const e=process.execPath;return W.join(W.dirname(e),z.NPM_TOOL)},j.executeCommand=function(e,t,n){0!==(0,U.spawnSync)(e,t,n).status&&(0,K.logErrorAndExit)(`Error: ${e} ${t} execute failed.See above for details.`)},j.offlinePluginConversion=function(e,t){return t.startsWith("file:")||t.endsWith(".tgz")?W.resolve(e,z.HVIGOR,t.replace("file:","")):t},j.isFileExists=function(e){return q.default.existsSync(e)&&q.default.statSync(e).isFile()},function(e){var t=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),n=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),r=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var r={};if(null!=e)for(var u in e)"default"!==u&&Object.prototype.hasOwnProperty.call(e,u)&&t(r,e,u);return n(r,e),r},u=w&&w.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(e,"__esModule",{value:!0}),e.executeInstallPnpm=e.isPnpmAvailable=e.environmentHandler=e.checkNpmConifg=e.PNPM_VERSION=void 0;const o=r(D.default),i=b,s=j,c=r(p.default),a=$,l=h.default,f=u(E.default);e.PNPM_VERSION="7.30.0",e.checkNpmConifg=function(){const e=c.resolve(i.HVIGOR_PROJECT_ROOT_DIR,".npmrc"),t=c.resolve(f.default.homedir(),".npmrc");if((0,s.isFileExists)(e)||(0,s.isFileExists)(t))return;const n=(0,s.getNpmPath)(),r=(0,l.spawnSync)(n,["config","get","prefix"],{cwd:i.HVIGOR_PROJECT_ROOT_DIR});if(0!==r.status||!r.stdout)return void(0,a.logErrorAndExit)("Error: The hvigor depends on the npmrc file. Configure the npmrc file first.");const u=c.resolve(`${r.stdout}`.replace(/[\r\n]/gi,""),".npmrc");(0,s.isFileExists)(u)||(0,a.logErrorAndExit)("Error: The hvigor depends on the npmrc file. Configure the npmrc file first.")},e.environmentHandler=function(){process.env["npm_config_update-notifier"]="false"},e.isPnpmAvailable=function(){return!!o.existsSync(i.HVIGOR_WRAPPER_PNPM_SCRIPT_PATH)&&(0,s.hasNpmPackInPaths)("pnpm",[i.HVIGOR_WRAPPER_TOOLS_HOME])},e.executeInstallPnpm=function(){(0,a.logInfoPrintConsole)(`Installing pnpm@${e.PNPM_VERSION}...`);const t=(0,s.getNpmPath)();!function(){const t=c.resolve(i.HVIGOR_WRAPPER_TOOLS_HOME,i.DEFAULT_PACKAGE_JSON);try{o.existsSync(i.HVIGOR_WRAPPER_TOOLS_HOME)||o.mkdirSync(i.HVIGOR_WRAPPER_TOOLS_HOME,{recursive:!0});const n={dependencies:{}};n.dependencies[i.PNPM]=e.PNPM_VERSION,o.writeFileSync(t,JSON.stringify(n))}catch(e){(0,a.logErrorAndExit)(`Error: EPERM: operation not permitted,create ${t} failed.`)}}(),(0,s.executeCommand)(t,["install","pnpm"],{cwd:i.HVIGOR_WRAPPER_TOOLS_HOME,stdio:["inherit","inherit","inherit"],env:process.env}),(0,a.logInfoPrintConsole)("Pnpm install success.")}}(O);var Y={},X={},Z={},Q={};Object.defineProperty(Q,"__esModule",{value:!0}),Q.Unicode=void 0;class ee{}Q.Unicode=ee,ee.Space_Separator=/[\u1680\u2000-\u200A\u202F\u205F\u3000]/,ee.ID_Start=/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE83\uDE86-\uDE89\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4\uDD00-\uDD43]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]/,ee.ID_Continue=/[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u08D4-\u08E1\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u09FC\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9-\u0AFF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C80-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D00-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D54-\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1CD0-\u1CD2\u1CD4-\u1CF9\u1D00-\u1DF9\u1DFB-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FEA\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AE\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C5\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF2D-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE3E\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC00-\uDC4A\uDC50-\uDC59\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDE00-\uDE3E\uDE47\uDE50-\uDE83\uDE86-\uDE99\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC36\uDC38-\uDC40\uDC50-\uDC59\uDC72-\uDC8F\uDC92-\uDCA7\uDCA9-\uDCB6\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD36\uDD3A\uDD3C\uDD3D\uDD3F-\uDD47\uDD50-\uDD59]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F\uDFE0\uDFE1]|\uD821[\uDC00-\uDFEC]|\uD822[\uDC00-\uDEF2]|\uD82C[\uDC00-\uDD1E\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD838[\uDC00-\uDC06\uDC08-\uDC18\uDC1B-\uDC21\uDC23\uDC24\uDC26-\uDC2A]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6\uDD00-\uDD4A\uDD50-\uDD59]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/,Object.defineProperty(Z,"__esModule",{value:!0}),Z.JudgeUtil=void 0;const te=Q;Z.JudgeUtil=class{static isIgnoreChar(e){return"string"==typeof e&&("\t"===e||"\v"===e||"\f"===e||" "===e||" "===e||"\ufeff"===e||"\n"===e||"\r"===e||"\u2028"===e||"\u2029"===e)}static isSpaceSeparator(e){return"string"==typeof e&&te.Unicode.Space_Separator.test(e)}static isIdStartChar(e){return"string"==typeof e&&(e>="a"&&e<="z"||e>="A"&&e<="Z"||"$"===e||"_"===e||te.Unicode.ID_Start.test(e))}static isIdContinueChar(e){return"string"==typeof e&&(e>="a"&&e<="z"||e>="A"&&e<="Z"||e>="0"&&e<="9"||"$"===e||"_"===e||"‌"===e||"‍"===e||te.Unicode.ID_Continue.test(e))}static isDigitWithoutZero(e){return/[1-9]/.test(e)}static isDigit(e){return"string"==typeof e&&/[0-9]/.test(e)}static isHexDigit(e){return"string"==typeof e&&/[0-9A-Fa-f]/.test(e)}};var ne={},re={fromCallback:function(e){return Object.defineProperty((function(...t){if("function"!=typeof t[t.length-1])return new Promise(((n,r)=>{e.call(this,...t,((e,t)=>null!=e?r(e):n(t)))}));e.apply(this,t)}),"name",{value:e.name})},fromPromise:function(e){return Object.defineProperty((function(...t){const n=t[t.length-1];if("function"!=typeof n)return e.apply(this,t);e.apply(this,t.slice(0,-1)).then((e=>n(null,e)),n)}),"name",{value:e.name})}},ue=y.default,oe=process.cwd,ie=null,se=process.env.GRACEFUL_FS_PLATFORM||process.platform;process.cwd=function(){return ie||(ie=oe.call(process)),ie};try{process.cwd()}catch(e){}if("function"==typeof process.chdir){var ce=process.chdir;process.chdir=function(e){ie=null,ce.call(process,e)},Object.setPrototypeOf&&Object.setPrototypeOf(process.chdir,ce)}var ae=function(e){ue.hasOwnProperty("O_SYMLINK")&&process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)&&function(e){e.lchmod=function(t,n,r){e.open(t,ue.O_WRONLY|ue.O_SYMLINK,n,(function(t,u){t?r&&r(t):e.fchmod(u,n,(function(t){e.close(u,(function(e){r&&r(t||e)}))}))}))},e.lchmodSync=function(t,n){var r,u=e.openSync(t,ue.O_WRONLY|ue.O_SYMLINK,n),o=!0;try{r=e.fchmodSync(u,n),o=!1}finally{if(o)try{e.closeSync(u)}catch(e){}else e.closeSync(u)}return r}}(e);e.lutimes||function(e){ue.hasOwnProperty("O_SYMLINK")&&e.futimes?(e.lutimes=function(t,n,r,u){e.open(t,ue.O_SYMLINK,(function(t,o){t?u&&u(t):e.futimes(o,n,r,(function(t){e.close(o,(function(e){u&&u(t||e)}))}))}))},e.lutimesSync=function(t,n,r){var u,o=e.openSync(t,ue.O_SYMLINK),i=!0;try{u=e.futimesSync(o,n,r),i=!1}finally{if(i)try{e.closeSync(o)}catch(e){}else e.closeSync(o)}return u}):e.futimes&&(e.lutimes=function(e,t,n,r){r&&process.nextTick(r)},e.lutimesSync=function(){})}(e);e.chown=r(e.chown),e.fchown=r(e.fchown),e.lchown=r(e.lchown),e.chmod=t(e.chmod),e.fchmod=t(e.fchmod),e.lchmod=t(e.lchmod),e.chownSync=u(e.chownSync),e.fchownSync=u(e.fchownSync),e.lchownSync=u(e.lchownSync),e.chmodSync=n(e.chmodSync),e.fchmodSync=n(e.fchmodSync),e.lchmodSync=n(e.lchmodSync),e.stat=o(e.stat),e.fstat=o(e.fstat),e.lstat=o(e.lstat),e.statSync=i(e.statSync),e.fstatSync=i(e.fstatSync),e.lstatSync=i(e.lstatSync),e.chmod&&!e.lchmod&&(e.lchmod=function(e,t,n){n&&process.nextTick(n)},e.lchmodSync=function(){});e.chown&&!e.lchown&&(e.lchown=function(e,t,n,r){r&&process.nextTick(r)},e.lchownSync=function(){});"win32"===se&&(e.rename="function"!=typeof e.rename?e.rename:function(t){function n(n,r,u){var o=Date.now(),i=0;t(n,r,(function s(c){if(c&&("EACCES"===c.code||"EPERM"===c.code||"EBUSY"===c.code)&&Date.now()-o<6e4)return setTimeout((function(){e.stat(r,(function(e,o){e&&"ENOENT"===e.code?t(n,r,s):u(c)}))}),i),void(i<100&&(i+=10));u&&u(c)}))}return Object.setPrototypeOf&&Object.setPrototypeOf(n,t),n}(e.rename));function t(t){return t?function(n,r,u){return t.call(e,n,r,(function(e){s(e)&&(e=null),u&&u.apply(this,arguments)}))}:t}function n(t){return t?function(n,r){try{return t.call(e,n,r)}catch(e){if(!s(e))throw e}}:t}function r(t){return t?function(n,r,u,o){return t.call(e,n,r,u,(function(e){s(e)&&(e=null),o&&o.apply(this,arguments)}))}:t}function u(t){return t?function(n,r,u){try{return t.call(e,n,r,u)}catch(e){if(!s(e))throw e}}:t}function o(t){return t?function(n,r,u){function o(e,t){t&&(t.uid<0&&(t.uid+=4294967296),t.gid<0&&(t.gid+=4294967296)),u&&u.apply(this,arguments)}return"function"==typeof r&&(u=r,r=null),r?t.call(e,n,r,o):t.call(e,n,o)}:t}function i(t){return t?function(n,r){var u=r?t.call(e,n,r):t.call(e,n);return u&&(u.uid<0&&(u.uid+=4294967296),u.gid<0&&(u.gid+=4294967296)),u}:t}function s(e){return!e||("ENOSYS"===e.code||!(process.getuid&&0===process.getuid()||"EINVAL"!==e.code&&"EPERM"!==e.code))}e.read="function"!=typeof e.read?e.read:function(t){function n(n,r,u,o,i,s){var c;if(s&&"function"==typeof s){var a=0;c=function(l,f,d){if(l&&"EAGAIN"===l.code&&a<10)return a++,t.call(e,n,r,u,o,i,c);s.apply(this,arguments)}}return t.call(e,n,r,u,o,i,c)}return Object.setPrototypeOf&&Object.setPrototypeOf(n,t),n}(e.read),e.readSync="function"!=typeof e.readSync?e.readSync:(c=e.readSync,function(t,n,r,u,o){for(var i=0;;)try{return c.call(e,t,n,r,u,o)}catch(e){if("EAGAIN"===e.code&&i<10){i++;continue}throw e}});var c};var le=C.default.Stream,fe=function(e){return{ReadStream:function t(n,r){if(!(this instanceof t))return new t(n,r);le.call(this);var u=this;this.path=n,this.fd=null,this.readable=!0,this.paused=!1,this.flags="r",this.mode=438,this.bufferSize=65536,r=r||{};for(var o=Object.keys(r),i=0,s=o.length;ithis.end)throw new Error("start must be <= end");this.pos=this.start}if(null!==this.fd)return void process.nextTick((function(){u._read()}));e.open(this.path,this.flags,this.mode,(function(e,t){if(e)return u.emit("error",e),void(u.readable=!1);u.fd=t,u.emit("open",t),u._read()}))},WriteStream:function t(n,r){if(!(this instanceof t))return new t(n,r);le.call(this),this.path=n,this.fd=null,this.writable=!0,this.flags="w",this.encoding="binary",this.mode=438,this.bytesWritten=0,r=r||{};for(var u=Object.keys(r),o=0,i=u.length;o= zero");this.pos=this.start}this.busy=!1,this._queue=[],null===this.fd&&(this._open=e.open,this._queue.push([this._open,this.path,this.flags,this.mode,void 0]),this.flush())}}};var de=function(e){if(null===e||"object"!=typeof e)return e;if(e instanceof Object)var t={__proto__:De(e)};else t=Object.create(null);return Object.getOwnPropertyNames(e).forEach((function(n){Object.defineProperty(t,n,Object.getOwnPropertyDescriptor(e,n))})),t},De=Object.getPrototypeOf||function(e){return e.__proto__};var pe,Ee,me=D.default,he=ae,ye=fe,Ce=de,Fe=F.default;function ge(e,t){Object.defineProperty(e,pe,{get:function(){return t}})}"function"==typeof Symbol&&"function"==typeof Symbol.for?(pe=Symbol.for("graceful-fs.queue"),Ee=Symbol.for("graceful-fs.previous")):(pe="___graceful-fs.queue",Ee="___graceful-fs.previous");var Ae=function(){};if(Fe.debuglog?Ae=Fe.debuglog("gfs4"):/\bgfs4\b/i.test(process.env.NODE_DEBUG||"")&&(Ae=function(){var e=Fe.format.apply(Fe,arguments);e="GFS4: "+e.split(/\n/).join("\nGFS4: "),console.error(e)}),!me[pe]){var ve=w[pe]||[];ge(me,ve),me.close=function(e){function t(t,n){return e.call(me,t,(function(e){e||_e(),"function"==typeof n&&n.apply(this,arguments)}))}return Object.defineProperty(t,Ee,{value:e}),t}(me.close),me.closeSync=function(e){function t(t){e.apply(me,arguments),_e()}return Object.defineProperty(t,Ee,{value:e}),t}(me.closeSync),/\bgfs4\b/i.test(process.env.NODE_DEBUG||"")&&process.on("exit",(function(){Ae(me[pe]),g.default.equal(me[pe].length,0)}))}w[pe]||ge(w,me[pe]);var Se,we=Oe(Ce(me));function Oe(e){he(e),e.gracefulify=Oe,e.createReadStream=function(t,n){return new e.ReadStream(t,n)},e.createWriteStream=function(t,n){return new e.WriteStream(t,n)};var t=e.readFile;e.readFile=function(e,n,r){"function"==typeof n&&(r=n,n=null);return function e(n,r,u,o){return t(n,r,(function(t){!t||"EMFILE"!==t.code&&"ENFILE"!==t.code?"function"==typeof u&&u.apply(this,arguments):be([e,[n,r,u],t,o||Date.now(),Date.now()])}))}(e,n,r)};var n=e.writeFile;e.writeFile=function(e,t,r,u){"function"==typeof r&&(u=r,r=null);return function e(t,r,u,o,i){return n(t,r,u,(function(n){!n||"EMFILE"!==n.code&&"ENFILE"!==n.code?"function"==typeof o&&o.apply(this,arguments):be([e,[t,r,u,o],n,i||Date.now(),Date.now()])}))}(e,t,r,u)};var r=e.appendFile;r&&(e.appendFile=function(e,t,n,u){"function"==typeof n&&(u=n,n=null);return function e(t,n,u,o,i){return r(t,n,u,(function(r){!r||"EMFILE"!==r.code&&"ENFILE"!==r.code?"function"==typeof o&&o.apply(this,arguments):be([e,[t,n,u,o],r,i||Date.now(),Date.now()])}))}(e,t,n,u)});var u=e.copyFile;u&&(e.copyFile=function(e,t,n,r){"function"==typeof n&&(r=n,n=0);return function e(t,n,r,o,i){return u(t,n,r,(function(u){!u||"EMFILE"!==u.code&&"ENFILE"!==u.code?"function"==typeof o&&o.apply(this,arguments):be([e,[t,n,r,o],u,i||Date.now(),Date.now()])}))}(e,t,n,r)});var o=e.readdir;e.readdir=function(e,t,n){"function"==typeof t&&(n=t,t=null);var r=i.test(process.version)?function(e,t,n,r){return o(e,u(e,t,n,r))}:function(e,t,n,r){return o(e,t,u(e,t,n,r))};return r(e,t,n);function u(e,t,n,u){return function(o,i){!o||"EMFILE"!==o.code&&"ENFILE"!==o.code?(i&&i.sort&&i.sort(),"function"==typeof n&&n.call(this,o,i)):be([r,[e,t,n],o,u||Date.now(),Date.now()])}}};var i=/^v[0-5]\./;if("v0.8"===process.version.substr(0,4)){var s=ye(e);d=s.ReadStream,D=s.WriteStream}var c=e.ReadStream;c&&(d.prototype=Object.create(c.prototype),d.prototype.open=function(){var e=this;E(e.path,e.flags,e.mode,(function(t,n){t?(e.autoClose&&e.destroy(),e.emit("error",t)):(e.fd=n,e.emit("open",n),e.read())}))});var a=e.WriteStream;a&&(D.prototype=Object.create(a.prototype),D.prototype.open=function(){var e=this;E(e.path,e.flags,e.mode,(function(t,n){t?(e.destroy(),e.emit("error",t)):(e.fd=n,e.emit("open",n))}))}),Object.defineProperty(e,"ReadStream",{get:function(){return d},set:function(e){d=e},enumerable:!0,configurable:!0}),Object.defineProperty(e,"WriteStream",{get:function(){return D},set:function(e){D=e},enumerable:!0,configurable:!0});var l=d;Object.defineProperty(e,"FileReadStream",{get:function(){return l},set:function(e){l=e},enumerable:!0,configurable:!0});var f=D;function d(e,t){return this instanceof d?(c.apply(this,arguments),this):d.apply(Object.create(d.prototype),arguments)}function D(e,t){return this instanceof D?(a.apply(this,arguments),this):D.apply(Object.create(D.prototype),arguments)}Object.defineProperty(e,"FileWriteStream",{get:function(){return f},set:function(e){f=e},enumerable:!0,configurable:!0});var p=e.open;function E(e,t,n,r){return"function"==typeof n&&(r=n,n=null),function e(t,n,r,u,o){return p(t,n,r,(function(i,s){!i||"EMFILE"!==i.code&&"ENFILE"!==i.code?"function"==typeof u&&u.apply(this,arguments):be([e,[t,n,r,u],i,o||Date.now(),Date.now()])}))}(e,t,n,r)}return e.open=E,e}function be(e){Ae("ENQUEUE",e[0].name,e[1]),me[pe].push(e),Be()}function _e(){for(var e=Date.now(),t=0;t2&&(me[pe][t][3]=e,me[pe][t][4]=e);Be()}function Be(){if(clearTimeout(Se),Se=void 0,0!==me[pe].length){var e=me[pe].shift(),t=e[0],n=e[1],r=e[2],u=e[3],o=e[4];if(void 0===u)Ae("RETRY",t.name,n),t.apply(null,n);else if(Date.now()-u>=6e4){Ae("TIMEOUT",t.name,n);var i=n.pop();"function"==typeof i&&i.call(null,r)}else{var s=Date.now()-o,c=Math.max(o-u,1);s>=Math.min(1.2*c,100)?(Ae("RETRY",t.name,n),t.apply(null,n.concat([u]))):me[pe].push(e)}void 0===Se&&(Se=setTimeout(Be,0))}}process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH&&!me.__patched&&(we=Oe(me),me.__patched=!0),function(e){const t=re.fromCallback,n=we,r=["access","appendFile","chmod","chown","close","copyFile","fchmod","fchown","fdatasync","fstat","fsync","ftruncate","futimes","lchmod","lchown","link","lstat","mkdir","mkdtemp","open","opendir","readdir","readFile","readlink","realpath","rename","rm","rmdir","stat","symlink","truncate","unlink","utimes","writeFile"].filter((e=>"function"==typeof n[e]));Object.assign(e,n),r.forEach((r=>{e[r]=t(n[r])})),e.realpath.native=t(n.realpath.native),e.exists=function(e,t){return"function"==typeof t?n.exists(e,t):new Promise((t=>n.exists(e,t)))},e.read=function(e,t,r,u,o,i){return"function"==typeof i?n.read(e,t,r,u,o,i):new Promise(((i,s)=>{n.read(e,t,r,u,o,((e,t,n)=>{if(e)return s(e);i({bytesRead:t,buffer:n})}))}))},e.write=function(e,t,...r){return"function"==typeof r[r.length-1]?n.write(e,t,...r):new Promise(((u,o)=>{n.write(e,t,...r,((e,t,n)=>{if(e)return o(e);u({bytesWritten:t,buffer:n})}))}))},"function"==typeof n.writev&&(e.writev=function(e,t,...r){return"function"==typeof r[r.length-1]?n.writev(e,t,...r):new Promise(((u,o)=>{n.writev(e,t,...r,((e,t,n)=>{if(e)return o(e);u({bytesWritten:t,buffers:n})}))}))})}(ne);var Pe={},ke={};const xe=p.default;ke.checkPath=function(e){if("win32"===process.platform){if(/[<>:"|?*]/.test(e.replace(xe.parse(e).root,""))){const t=new Error(`Path contains invalid characters: ${e}`);throw t.code="EINVAL",t}}};const Ne=ne,{checkPath:Ie}=ke,Te=e=>"number"==typeof e?e:{mode:511,...e}.mode;Pe.makeDir=async(e,t)=>(Ie(e),Ne.mkdir(e,{mode:Te(t),recursive:!0})),Pe.makeDirSync=(e,t)=>(Ie(e),Ne.mkdirSync(e,{mode:Te(t),recursive:!0}));const Re=re.fromPromise,{makeDir:Me,makeDirSync:Le}=Pe,je=Re(Me);var $e={mkdirs:je,mkdirsSync:Le,mkdirp:je,mkdirpSync:Le,ensureDir:je,ensureDirSync:Le};const He=re.fromPromise,Je=ne;var Ge={pathExists:He((function(e){return Je.access(e).then((()=>!0)).catch((()=>!1))})),pathExistsSync:Je.existsSync};const Ve=we;var Ue=function(e,t,n,r){Ve.open(e,"r+",((e,u)=>{if(e)return r(e);Ve.futimes(u,t,n,(e=>{Ve.close(u,(t=>{r&&r(e||t)}))}))}))},We=function(e,t,n){const r=Ve.openSync(e,"r+");return Ve.futimesSync(r,t,n),Ve.closeSync(r)};const ze=ne,Ke=p.default,qe=F.default;function Ye(e,t,n){const r=n.dereference?e=>ze.stat(e,{bigint:!0}):e=>ze.lstat(e,{bigint:!0});return Promise.all([r(e),r(t).catch((e=>{if("ENOENT"===e.code)return null;throw e}))]).then((([e,t])=>({srcStat:e,destStat:t})))}function Xe(e,t){return t.ino&&t.dev&&t.ino===e.ino&&t.dev===e.dev}function Ze(e,t){const n=Ke.resolve(e).split(Ke.sep).filter((e=>e)),r=Ke.resolve(t).split(Ke.sep).filter((e=>e));return n.reduce(((e,t,n)=>e&&r[n]===t),!0)}function Qe(e,t,n){return`Cannot ${n} '${e}' to a subdirectory of itself, '${t}'.`}var et={checkPaths:function(e,t,n,r,u){qe.callbackify(Ye)(e,t,r,((r,o)=>{if(r)return u(r);const{srcStat:i,destStat:s}=o;if(s){if(Xe(i,s)){const r=Ke.basename(e),o=Ke.basename(t);return"move"===n&&r!==o&&r.toLowerCase()===o.toLowerCase()?u(null,{srcStat:i,destStat:s,isChangingCase:!0}):u(new Error("Source and destination must not be the same."))}if(i.isDirectory()&&!s.isDirectory())return u(new Error(`Cannot overwrite non-directory '${t}' with directory '${e}'.`));if(!i.isDirectory()&&s.isDirectory())return u(new Error(`Cannot overwrite directory '${t}' with non-directory '${e}'.`))}return i.isDirectory()&&Ze(e,t)?u(new Error(Qe(e,t,n))):u(null,{srcStat:i,destStat:s})}))},checkPathsSync:function(e,t,n,r){const{srcStat:u,destStat:o}=function(e,t,n){let r;const u=n.dereference?e=>ze.statSync(e,{bigint:!0}):e=>ze.lstatSync(e,{bigint:!0}),o=u(e);try{r=u(t)}catch(e){if("ENOENT"===e.code)return{srcStat:o,destStat:null};throw e}return{srcStat:o,destStat:r}}(e,t,r);if(o){if(Xe(u,o)){const r=Ke.basename(e),i=Ke.basename(t);if("move"===n&&r!==i&&r.toLowerCase()===i.toLowerCase())return{srcStat:u,destStat:o,isChangingCase:!0};throw new Error("Source and destination must not be the same.")}if(u.isDirectory()&&!o.isDirectory())throw new Error(`Cannot overwrite non-directory '${t}' with directory '${e}'.`);if(!u.isDirectory()&&o.isDirectory())throw new Error(`Cannot overwrite directory '${t}' with non-directory '${e}'.`)}if(u.isDirectory()&&Ze(e,t))throw new Error(Qe(e,t,n));return{srcStat:u,destStat:o}},checkParentPaths:function e(t,n,r,u,o){const i=Ke.resolve(Ke.dirname(t)),s=Ke.resolve(Ke.dirname(r));if(s===i||s===Ke.parse(s).root)return o();ze.stat(s,{bigint:!0},((i,c)=>i?"ENOENT"===i.code?o():o(i):Xe(n,c)?o(new Error(Qe(t,r,u))):e(t,n,s,u,o)))},checkParentPathsSync:function e(t,n,r,u){const o=Ke.resolve(Ke.dirname(t)),i=Ke.resolve(Ke.dirname(r));if(i===o||i===Ke.parse(i).root)return;let s;try{s=ze.statSync(i,{bigint:!0})}catch(e){if("ENOENT"===e.code)return;throw e}if(Xe(n,s))throw new Error(Qe(t,r,u));return e(t,n,i,u)},isSrcSubdir:Ze,areIdentical:Xe};const tt=we,nt=p.default,rt=$e.mkdirs,ut=Ge.pathExists,ot=Ue,it=et;function st(e,t,n,r,u){const o=nt.dirname(n);ut(o,((i,s)=>i?u(i):s?at(e,t,n,r,u):void rt(o,(o=>o?u(o):at(e,t,n,r,u)))))}function ct(e,t,n,r,u,o){Promise.resolve(u.filter(n,r)).then((i=>i?e(t,n,r,u,o):o()),(e=>o(e)))}function at(e,t,n,r,u){(r.dereference?tt.stat:tt.lstat)(t,((o,i)=>o?u(o):i.isDirectory()?function(e,t,n,r,u,o){return t?Dt(n,r,u,o):function(e,t,n,r,u){tt.mkdir(n,(o=>{if(o)return u(o);Dt(t,n,r,(t=>t?u(t):dt(n,e,u)))}))}(e.mode,n,r,u,o)}(i,e,t,n,r,u):i.isFile()||i.isCharacterDevice()||i.isBlockDevice()?function(e,t,n,r,u,o){return t?function(e,t,n,r,u){if(!r.overwrite)return r.errorOnExist?u(new Error(`'${n}' already exists`)):u();tt.unlink(n,(o=>o?u(o):lt(e,t,n,r,u)))}(e,n,r,u,o):lt(e,n,r,u,o)}(i,e,t,n,r,u):i.isSymbolicLink()?function(e,t,n,r,u){tt.readlink(t,((t,o)=>t?u(t):(r.dereference&&(o=nt.resolve(process.cwd(),o)),e?void tt.readlink(n,((t,i)=>t?"EINVAL"===t.code||"UNKNOWN"===t.code?tt.symlink(o,n,u):u(t):(r.dereference&&(i=nt.resolve(process.cwd(),i)),it.isSrcSubdir(o,i)?u(new Error(`Cannot copy '${o}' to a subdirectory of itself, '${i}'.`)):e.isDirectory()&&it.isSrcSubdir(i,o)?u(new Error(`Cannot overwrite '${i}' with '${o}'.`)):function(e,t,n){tt.unlink(t,(r=>r?n(r):tt.symlink(e,t,n)))}(o,n,u)))):tt.symlink(o,n,u))))}(e,t,n,r,u):i.isSocket()?u(new Error(`Cannot copy a socket file: ${t}`)):i.isFIFO()?u(new Error(`Cannot copy a FIFO pipe: ${t}`)):u(new Error(`Unknown file: ${t}`))))}function lt(e,t,n,r,u){tt.copyFile(t,n,(o=>o?u(o):r.preserveTimestamps?function(e,t,n,r){if(function(e){return 0==(128&e)}(e))return function(e,t,n){return dt(e,128|t,n)}(n,e,(u=>u?r(u):ft(e,t,n,r)));return ft(e,t,n,r)}(e.mode,t,n,u):dt(n,e.mode,u)))}function ft(e,t,n,r){!function(e,t,n){tt.stat(e,((e,r)=>e?n(e):ot(t,r.atime,r.mtime,n)))}(t,n,(t=>t?r(t):dt(n,e,r)))}function dt(e,t,n){return tt.chmod(e,t,n)}function Dt(e,t,n,r){tt.readdir(e,((u,o)=>u?r(u):pt(o,e,t,n,r)))}function pt(e,t,n,r,u){const o=e.pop();return o?function(e,t,n,r,u,o){const i=nt.join(n,t),s=nt.join(r,t);it.checkPaths(i,s,"copy",u,((t,c)=>{if(t)return o(t);const{destStat:a}=c;!function(e,t,n,r,u){r.filter?ct(at,e,t,n,r,u):at(e,t,n,r,u)}(a,i,s,u,(t=>t?o(t):pt(e,n,r,u,o)))}))}(e,o,t,n,r,u):u()}var Et=function(e,t,n,r){"function"!=typeof n||r?"function"==typeof n&&(n={filter:n}):(r=n,n={}),r=r||function(){},(n=n||{}).clobber=!("clobber"in n)||!!n.clobber,n.overwrite="overwrite"in n?!!n.overwrite:n.clobber,n.preserveTimestamps&&"ia32"===process.arch&&console.warn("fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n\n see https://github.com/jprichardson/node-fs-extra/issues/269"),it.checkPaths(e,t,"copy",n,((u,o)=>{if(u)return r(u);const{srcStat:i,destStat:s}=o;it.checkParentPaths(e,i,t,"copy",(u=>u?r(u):n.filter?ct(st,s,e,t,n,r):st(s,e,t,n,r)))}))};const mt=we,ht=p.default,yt=$e.mkdirsSync,Ct=We,Ft=et;function gt(e,t,n,r){const u=(r.dereference?mt.statSync:mt.lstatSync)(t);if(u.isDirectory())return function(e,t,n,r,u){return t?St(n,r,u):function(e,t,n,r){return mt.mkdirSync(n),St(t,n,r),vt(n,e)}(e.mode,n,r,u)}(u,e,t,n,r);if(u.isFile()||u.isCharacterDevice()||u.isBlockDevice())return function(e,t,n,r,u){return t?function(e,t,n,r){if(r.overwrite)return mt.unlinkSync(n),At(e,t,n,r);if(r.errorOnExist)throw new Error(`'${n}' already exists`)}(e,n,r,u):At(e,n,r,u)}(u,e,t,n,r);if(u.isSymbolicLink())return function(e,t,n,r){let u=mt.readlinkSync(t);r.dereference&&(u=ht.resolve(process.cwd(),u));if(e){let e;try{e=mt.readlinkSync(n)}catch(e){if("EINVAL"===e.code||"UNKNOWN"===e.code)return mt.symlinkSync(u,n);throw e}if(r.dereference&&(e=ht.resolve(process.cwd(),e)),Ft.isSrcSubdir(u,e))throw new Error(`Cannot copy '${u}' to a subdirectory of itself, '${e}'.`);if(mt.statSync(n).isDirectory()&&Ft.isSrcSubdir(e,u))throw new Error(`Cannot overwrite '${e}' with '${u}'.`);return function(e,t){return mt.unlinkSync(t),mt.symlinkSync(e,t)}(u,n)}return mt.symlinkSync(u,n)}(e,t,n,r);if(u.isSocket())throw new Error(`Cannot copy a socket file: ${t}`);if(u.isFIFO())throw new Error(`Cannot copy a FIFO pipe: ${t}`);throw new Error(`Unknown file: ${t}`)}function At(e,t,n,r){return mt.copyFileSync(t,n),r.preserveTimestamps&&function(e,t,n){(function(e){return 0==(128&e)})(e)&&function(e,t){vt(e,128|t)}(n,e);(function(e,t){const n=mt.statSync(e);Ct(t,n.atime,n.mtime)})(t,n)}(e.mode,t,n),vt(n,e.mode)}function vt(e,t){return mt.chmodSync(e,t)}function St(e,t,n){mt.readdirSync(e).forEach((r=>function(e,t,n,r){const u=ht.join(t,e),o=ht.join(n,e),{destStat:i}=Ft.checkPathsSync(u,o,"copy",r);return function(e,t,n,r){if(!r.filter||r.filter(t,n))return gt(e,t,n,r)}(i,u,o,r)}(r,e,t,n)))}var wt=function(e,t,n){"function"==typeof n&&(n={filter:n}),(n=n||{}).clobber=!("clobber"in n)||!!n.clobber,n.overwrite="overwrite"in n?!!n.overwrite:n.clobber,n.preserveTimestamps&&"ia32"===process.arch&&console.warn("fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n\n see https://github.com/jprichardson/node-fs-extra/issues/269");const{srcStat:r,destStat:u}=Ft.checkPathsSync(e,t,"copy",n);return Ft.checkParentPathsSync(e,r,t,"copy"),function(e,t,n,r){if(r.filter&&!r.filter(t,n))return;const u=ht.dirname(n);mt.existsSync(u)||yt(u);return gt(e,t,n,r)}(u,e,t,n)};var Ot={copy:(0,re.fromCallback)(Et),copySync:wt};const bt=we,_t=p.default,Bt=g.default,Pt="win32"===process.platform;function kt(e){["unlink","chmod","stat","lstat","rmdir","readdir"].forEach((t=>{e[t]=e[t]||bt[t],e[t+="Sync"]=e[t]||bt[t]})),e.maxBusyTries=e.maxBusyTries||3}function xt(e,t,n){let r=0;"function"==typeof t&&(n=t,t={}),Bt(e,"rimraf: missing path"),Bt.strictEqual(typeof e,"string","rimraf: path should be a string"),Bt.strictEqual(typeof n,"function","rimraf: callback function required"),Bt(t,"rimraf: invalid options argument provided"),Bt.strictEqual(typeof t,"object","rimraf: options should be object"),kt(t),Nt(e,t,(function u(o){if(o){if(("EBUSY"===o.code||"ENOTEMPTY"===o.code||"EPERM"===o.code)&&rNt(e,t,u)),100*r)}"ENOENT"===o.code&&(o=null)}n(o)}))}function Nt(e,t,n){Bt(e),Bt(t),Bt("function"==typeof n),t.lstat(e,((r,u)=>r&&"ENOENT"===r.code?n(null):r&&"EPERM"===r.code&&Pt?It(e,t,r,n):u&&u.isDirectory()?Rt(e,t,r,n):void t.unlink(e,(r=>{if(r){if("ENOENT"===r.code)return n(null);if("EPERM"===r.code)return Pt?It(e,t,r,n):Rt(e,t,r,n);if("EISDIR"===r.code)return Rt(e,t,r,n)}return n(r)}))))}function It(e,t,n,r){Bt(e),Bt(t),Bt("function"==typeof r),t.chmod(e,438,(u=>{u?r("ENOENT"===u.code?null:n):t.stat(e,((u,o)=>{u?r("ENOENT"===u.code?null:n):o.isDirectory()?Rt(e,t,n,r):t.unlink(e,r)}))}))}function Tt(e,t,n){let r;Bt(e),Bt(t);try{t.chmodSync(e,438)}catch(e){if("ENOENT"===e.code)return;throw n}try{r=t.statSync(e)}catch(e){if("ENOENT"===e.code)return;throw n}r.isDirectory()?Lt(e,t,n):t.unlinkSync(e)}function Rt(e,t,n,r){Bt(e),Bt(t),Bt("function"==typeof r),t.rmdir(e,(u=>{!u||"ENOTEMPTY"!==u.code&&"EEXIST"!==u.code&&"EPERM"!==u.code?u&&"ENOTDIR"===u.code?r(n):r(u):function(e,t,n){Bt(e),Bt(t),Bt("function"==typeof n),t.readdir(e,((r,u)=>{if(r)return n(r);let o,i=u.length;if(0===i)return t.rmdir(e,n);u.forEach((r=>{xt(_t.join(e,r),t,(r=>{if(!o)return r?n(o=r):void(0==--i&&t.rmdir(e,n))}))}))}))}(e,t,r)}))}function Mt(e,t){let n;kt(t=t||{}),Bt(e,"rimraf: missing path"),Bt.strictEqual(typeof e,"string","rimraf: path should be a string"),Bt(t,"rimraf: missing options"),Bt.strictEqual(typeof t,"object","rimraf: options should be object");try{n=t.lstatSync(e)}catch(n){if("ENOENT"===n.code)return;"EPERM"===n.code&&Pt&&Tt(e,t,n)}try{n&&n.isDirectory()?Lt(e,t,null):t.unlinkSync(e)}catch(n){if("ENOENT"===n.code)return;if("EPERM"===n.code)return Pt?Tt(e,t,n):Lt(e,t,n);if("EISDIR"!==n.code)throw n;Lt(e,t,n)}}function Lt(e,t,n){Bt(e),Bt(t);try{t.rmdirSync(e)}catch(r){if("ENOTDIR"===r.code)throw n;if("ENOTEMPTY"===r.code||"EEXIST"===r.code||"EPERM"===r.code)!function(e,t){if(Bt(e),Bt(t),t.readdirSync(e).forEach((n=>Mt(_t.join(e,n),t))),!Pt){return t.rmdirSync(e,t)}{const n=Date.now();do{try{return t.rmdirSync(e,t)}catch{}}while(Date.now()-n<500)}}(e,t);else if("ENOENT"!==r.code)throw r}}var jt=xt;xt.sync=Mt;const $t=we,Ht=re.fromCallback,Jt=jt;var Gt={remove:Ht((function(e,t){if($t.rm)return $t.rm(e,{recursive:!0,force:!0},t);Jt(e,t)})),removeSync:function(e){if($t.rmSync)return $t.rmSync(e,{recursive:!0,force:!0});Jt.sync(e)}};const Vt=re.fromPromise,Ut=ne,Wt=p.default,zt=$e,Kt=Gt,qt=Vt((async function(e){let t;try{t=await Ut.readdir(e)}catch{return zt.mkdirs(e)}return Promise.all(t.map((t=>Kt.remove(Wt.join(e,t)))))}));function Yt(e){let t;try{t=Ut.readdirSync(e)}catch{return zt.mkdirsSync(e)}t.forEach((t=>{t=Wt.join(e,t),Kt.removeSync(t)}))}var Xt={emptyDirSync:Yt,emptydirSync:Yt,emptyDir:qt,emptydir:qt};const Zt=re.fromCallback,Qt=p.default,en=we,tn=$e;var nn={createFile:Zt((function(e,t){function n(){en.writeFile(e,"",(e=>{if(e)return t(e);t()}))}en.stat(e,((r,u)=>{if(!r&&u.isFile())return t();const o=Qt.dirname(e);en.stat(o,((e,r)=>{if(e)return"ENOENT"===e.code?tn.mkdirs(o,(e=>{if(e)return t(e);n()})):t(e);r.isDirectory()?n():en.readdir(o,(e=>{if(e)return t(e)}))}))}))})),createFileSync:function(e){let t;try{t=en.statSync(e)}catch{}if(t&&t.isFile())return;const n=Qt.dirname(e);try{en.statSync(n).isDirectory()||en.readdirSync(n)}catch(e){if(!e||"ENOENT"!==e.code)throw e;tn.mkdirsSync(n)}en.writeFileSync(e,"")}};const rn=re.fromCallback,un=p.default,on=we,sn=$e,cn=Ge.pathExists,{areIdentical:an}=et;var ln={createLink:rn((function(e,t,n){function r(e,t){on.link(e,t,(e=>{if(e)return n(e);n(null)}))}on.lstat(t,((u,o)=>{on.lstat(e,((u,i)=>{if(u)return u.message=u.message.replace("lstat","ensureLink"),n(u);if(o&&an(i,o))return n(null);const s=un.dirname(t);cn(s,((u,o)=>u?n(u):o?r(e,t):void sn.mkdirs(s,(u=>{if(u)return n(u);r(e,t)}))))}))}))})),createLinkSync:function(e,t){let n;try{n=on.lstatSync(t)}catch{}try{const t=on.lstatSync(e);if(n&&an(t,n))return}catch(e){throw e.message=e.message.replace("lstat","ensureLink"),e}const r=un.dirname(t);return on.existsSync(r)||sn.mkdirsSync(r),on.linkSync(e,t)}};const fn=p.default,dn=we,Dn=Ge.pathExists;var pn={symlinkPaths:function(e,t,n){if(fn.isAbsolute(e))return dn.lstat(e,(t=>t?(t.message=t.message.replace("lstat","ensureSymlink"),n(t)):n(null,{toCwd:e,toDst:e})));{const r=fn.dirname(t),u=fn.join(r,e);return Dn(u,((t,o)=>t?n(t):o?n(null,{toCwd:u,toDst:e}):dn.lstat(e,(t=>t?(t.message=t.message.replace("lstat","ensureSymlink"),n(t)):n(null,{toCwd:e,toDst:fn.relative(r,e)})))))}},symlinkPathsSync:function(e,t){let n;if(fn.isAbsolute(e)){if(n=dn.existsSync(e),!n)throw new Error("absolute srcpath does not exist");return{toCwd:e,toDst:e}}{const r=fn.dirname(t),u=fn.join(r,e);if(n=dn.existsSync(u),n)return{toCwd:u,toDst:e};if(n=dn.existsSync(e),!n)throw new Error("relative srcpath does not exist");return{toCwd:e,toDst:fn.relative(r,e)}}}};const En=we;var mn={symlinkType:function(e,t,n){if(n="function"==typeof t?t:n,t="function"!=typeof t&&t)return n(null,t);En.lstat(e,((e,r)=>{if(e)return n(null,"file");t=r&&r.isDirectory()?"dir":"file",n(null,t)}))},symlinkTypeSync:function(e,t){let n;if(t)return t;try{n=En.lstatSync(e)}catch{return"file"}return n&&n.isDirectory()?"dir":"file"}};const hn=re.fromCallback,yn=p.default,Cn=ne,Fn=$e.mkdirs,gn=$e.mkdirsSync,An=pn.symlinkPaths,vn=pn.symlinkPathsSync,Sn=mn.symlinkType,wn=mn.symlinkTypeSync,On=Ge.pathExists,{areIdentical:bn}=et;function _n(e,t,n,r){An(e,t,((u,o)=>{if(u)return r(u);e=o.toDst,Sn(o.toCwd,n,((n,u)=>{if(n)return r(n);const o=yn.dirname(t);On(o,((n,i)=>n?r(n):i?Cn.symlink(e,t,u,r):void Fn(o,(n=>{if(n)return r(n);Cn.symlink(e,t,u,r)}))))}))}))}var Bn={createSymlink:hn((function(e,t,n,r){r="function"==typeof n?n:r,n="function"!=typeof n&&n,Cn.lstat(t,((u,o)=>{!u&&o.isSymbolicLink()?Promise.all([Cn.stat(e),Cn.stat(t)]).then((([u,o])=>{if(bn(u,o))return r(null);_n(e,t,n,r)})):_n(e,t,n,r)}))})),createSymlinkSync:function(e,t,n){let r;try{r=Cn.lstatSync(t)}catch{}if(r&&r.isSymbolicLink()){const n=Cn.statSync(e),r=Cn.statSync(t);if(bn(n,r))return}const u=vn(e,t);e=u.toDst,n=wn(u.toCwd,n);const o=yn.dirname(t);return Cn.existsSync(o)||gn(o),Cn.symlinkSync(e,t,n)}};const{createFile:Pn,createFileSync:kn}=nn,{createLink:xn,createLinkSync:Nn}=ln,{createSymlink:In,createSymlinkSync:Tn}=Bn;var Rn={createFile:Pn,createFileSync:kn,ensureFile:Pn,ensureFileSync:kn,createLink:xn,createLinkSync:Nn,ensureLink:xn,ensureLinkSync:Nn,createSymlink:In,createSymlinkSync:Tn,ensureSymlink:In,ensureSymlinkSync:Tn};var Mn={stringify:function(e,{EOL:t="\n",finalEOL:n=!0,replacer:r=null,spaces:u}={}){const o=n?t:"";return JSON.stringify(e,r,u).replace(/\n/g,t)+o},stripBom:function(e){return Buffer.isBuffer(e)&&(e=e.toString("utf8")),e.replace(/^\uFEFF/,"")}};let Ln;try{Ln=we}catch(e){Ln=D.default}const jn=re,{stringify:$n,stripBom:Hn}=Mn;const Jn=jn.fromPromise((async function(e,t={}){"string"==typeof t&&(t={encoding:t});const n=t.fs||Ln,r=!("throws"in t)||t.throws;let u,o=await jn.fromCallback(n.readFile)(e,t);o=Hn(o);try{u=JSON.parse(o,t?t.reviver:null)}catch(t){if(r)throw t.message=`${e}: ${t.message}`,t;return null}return u}));const Gn=jn.fromPromise((async function(e,t,n={}){const r=n.fs||Ln,u=$n(t,n);await jn.fromCallback(r.writeFile)(e,u,n)}));const Vn={readFile:Jn,readFileSync:function(e,t={}){"string"==typeof t&&(t={encoding:t});const n=t.fs||Ln,r=!("throws"in t)||t.throws;try{let r=n.readFileSync(e,t);return r=Hn(r),JSON.parse(r,t.reviver)}catch(t){if(r)throw t.message=`${e}: ${t.message}`,t;return null}},writeFile:Gn,writeFileSync:function(e,t,n={}){const r=n.fs||Ln,u=$n(t,n);return r.writeFileSync(e,u,n)}};var Un={readJson:Vn.readFile,readJsonSync:Vn.readFileSync,writeJson:Vn.writeFile,writeJsonSync:Vn.writeFileSync};const Wn=re.fromCallback,zn=we,Kn=p.default,qn=$e,Yn=Ge.pathExists;var Xn={outputFile:Wn((function(e,t,n,r){"function"==typeof n&&(r=n,n="utf8");const u=Kn.dirname(e);Yn(u,((o,i)=>o?r(o):i?zn.writeFile(e,t,n,r):void qn.mkdirs(u,(u=>{if(u)return r(u);zn.writeFile(e,t,n,r)}))))})),outputFileSync:function(e,...t){const n=Kn.dirname(e);if(zn.existsSync(n))return zn.writeFileSync(e,...t);qn.mkdirsSync(n),zn.writeFileSync(e,...t)}};const{stringify:Zn}=Mn,{outputFile:Qn}=Xn;var er=async function(e,t,n={}){const r=Zn(t,n);await Qn(e,r,n)};const{stringify:tr}=Mn,{outputFileSync:nr}=Xn;var rr=function(e,t,n){const r=tr(t,n);nr(e,r,n)};const ur=re.fromPromise,or=Un;or.outputJson=ur(er),or.outputJsonSync=rr,or.outputJSON=or.outputJson,or.outputJSONSync=or.outputJsonSync,or.writeJSON=or.writeJson,or.writeJSONSync=or.writeJsonSync,or.readJSON=or.readJson,or.readJSONSync=or.readJsonSync;var ir=or;const sr=we,cr=p.default,ar=Ot.copy,lr=Gt.remove,fr=$e.mkdirp,dr=Ge.pathExists,Dr=et;function pr(e,t,n,r,u){return r?Er(e,t,n,u):n?lr(t,(r=>r?u(r):Er(e,t,n,u))):void dr(t,((r,o)=>r?u(r):o?u(new Error("dest already exists.")):Er(e,t,n,u)))}function Er(e,t,n,r){sr.rename(e,t,(u=>u?"EXDEV"!==u.code?r(u):function(e,t,n,r){const u={overwrite:n,errorOnExist:!0};ar(e,t,u,(t=>t?r(t):lr(e,r)))}(e,t,n,r):r()))}var mr=function(e,t,n,r){"function"==typeof n&&(r=n,n={});const u=n.overwrite||n.clobber||!1;Dr.checkPaths(e,t,"move",n,((n,o)=>{if(n)return r(n);const{srcStat:i,isChangingCase:s=!1}=o;Dr.checkParentPaths(e,i,t,"move",(n=>n?r(n):function(e){const t=cr.dirname(e);return cr.parse(t).root===t}(t)?pr(e,t,u,s,r):void fr(cr.dirname(t),(n=>n?r(n):pr(e,t,u,s,r)))))}))};const hr=we,yr=p.default,Cr=Ot.copySync,Fr=Gt.removeSync,gr=$e.mkdirpSync,Ar=et;function vr(e,t,n){try{hr.renameSync(e,t)}catch(r){if("EXDEV"!==r.code)throw r;return function(e,t,n){const r={overwrite:n,errorOnExist:!0};return Cr(e,t,r),Fr(e)}(e,t,n)}}var Sr=function(e,t,n){const r=(n=n||{}).overwrite||n.clobber||!1,{srcStat:u,isChangingCase:o=!1}=Ar.checkPathsSync(e,t,"move",n);return Ar.checkParentPathsSync(e,u,t,"move"),function(e){const t=yr.dirname(e);return yr.parse(t).root===t}(t)||gr(yr.dirname(t)),function(e,t,n,r){if(r)return vr(e,t,n);if(n)return Fr(t),vr(e,t,n);if(hr.existsSync(t))throw new Error("dest already exists.");return vr(e,t,n)}(e,t,r,o)};var wr,Or,br,_r,Br,Pr={move:(0,re.fromCallback)(mr),moveSync:Sr},kr={...ne,...Ot,...Xt,...Rn,...ir,...$e,...Pr,...Xn,...Ge,...Gt},xr={},Nr={exports:{}},Ir={exports:{}};function Tr(){if(Or)return wr;Or=1;var e=1e3,t=60*e,n=60*t,r=24*n,u=7*r,o=365.25*r;function i(e,t,n,r){var u=t>=1.5*n;return Math.round(e/n)+" "+r+(u?"s":"")}return wr=function(s,c){c=c||{};var a=typeof s;if("string"===a&&s.length>0)return function(i){if((i=String(i)).length>100)return;var s=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(i);if(!s)return;var c=parseFloat(s[1]);switch((s[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return c*o;case"weeks":case"week":case"w":return c*u;case"days":case"day":case"d":return c*r;case"hours":case"hour":case"hrs":case"hr":case"h":return c*n;case"minutes":case"minute":case"mins":case"min":case"m":return c*t;case"seconds":case"second":case"secs":case"sec":case"s":return c*e;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return c;default:return}}(s);if("number"===a&&isFinite(s))return c.long?function(u){var o=Math.abs(u);if(o>=r)return i(u,o,r,"day");if(o>=n)return i(u,o,n,"hour");if(o>=t)return i(u,o,t,"minute");if(o>=e)return i(u,o,e,"second");return u+" ms"}(s):function(u){var o=Math.abs(u);if(o>=r)return Math.round(u/r)+"d";if(o>=n)return Math.round(u/n)+"h";if(o>=t)return Math.round(u/t)+"m";if(o>=e)return Math.round(u/e)+"s";return u+"ms"}(s);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(s))}}function Rr(){if(_r)return br;return _r=1,br=function(e){function t(e){let r,u,o,i=null;function s(...e){if(!s.enabled)return;const n=s,u=Number(new Date),o=u-(r||u);n.diff=o,n.prev=r,n.curr=u,r=u,e[0]=t.coerce(e[0]),"string"!=typeof e[0]&&e.unshift("%O");let i=0;e[0]=e[0].replace(/%([a-zA-Z%])/g,((r,u)=>{if("%%"===r)return"%";i++;const o=t.formatters[u];if("function"==typeof o){const t=e[i];r=o.call(n,t),e.splice(i,1),i--}return r})),t.formatArgs.call(n,e);(n.log||t.log).apply(n,e)}return s.namespace=e,s.useColors=t.useColors(),s.color=t.selectColor(e),s.extend=n,s.destroy=t.destroy,Object.defineProperty(s,"enabled",{enumerable:!0,configurable:!1,get:()=>null!==i?i:(u!==t.namespaces&&(u=t.namespaces,o=t.enabled(e)),o),set:e=>{i=e}}),"function"==typeof t.init&&t.init(s),s}function n(e,n){const r=t(this.namespace+(void 0===n?":":n)+e);return r.log=this.log,r}function r(e){return e.toString().substring(2,e.toString().length-2).replace(/\.\*\?$/,"*")}return t.debug=t,t.default=t,t.coerce=function(e){if(e instanceof Error)return e.stack||e.message;return e},t.disable=function(){const e=[...t.names.map(r),...t.skips.map(r).map((e=>"-"+e))].join(",");return t.enable(""),e},t.enable=function(e){let n;t.save(e),t.namespaces=e,t.names=[],t.skips=[];const r=("string"==typeof e?e:"").split(/[\s,]+/),u=r.length;for(n=0;n{t[n]=e[n]})),t.names=[],t.skips=[],t.formatters={},t.selectColor=function(e){let n=0;for(let t=0;t{const n=e.startsWith("-")?"":1===e.length?"-":"--",r=t.indexOf(n+e),u=t.indexOf("--");return-1!==r&&(-1===u||r{}),"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."),t.colors=[6,2,3,4,5,1];try{const e=function(){if($r)return jr;$r=1;const e=E.default,t=A.default,n=Vr(),{env:r}=process;let u;function o(e){return 0!==e&&{level:e,hasBasic:!0,has256:e>=2,has16m:e>=3}}function i(t,o){if(0===u)return 0;if(n("color=16m")||n("color=full")||n("color=truecolor"))return 3;if(n("color=256"))return 2;if(t&&!o&&void 0===u)return 0;const i=u||0;if("dumb"===r.TERM)return i;if("win32"===process.platform){const t=e.release().split(".");return Number(t[0])>=10&&Number(t[2])>=10586?Number(t[2])>=14931?3:2:1}if("CI"in r)return["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI","GITHUB_ACTIONS","BUILDKITE"].some((e=>e in r))||"codeship"===r.CI_NAME?1:i;if("TEAMCITY_VERSION"in r)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(r.TEAMCITY_VERSION)?1:0;if("truecolor"===r.COLORTERM)return 3;if("TERM_PROGRAM"in r){const e=parseInt((r.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(r.TERM_PROGRAM){case"iTerm.app":return e>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(r.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(r.TERM)||"COLORTERM"in r?1:i}return n("no-color")||n("no-colors")||n("color=false")||n("color=never")?u=0:(n("color")||n("colors")||n("color=true")||n("color=always"))&&(u=1),"FORCE_COLOR"in r&&(u="true"===r.FORCE_COLOR?1:"false"===r.FORCE_COLOR?0:0===r.FORCE_COLOR.length?1:Math.min(parseInt(r.FORCE_COLOR,10),3)),jr={supportsColor:function(e){return o(i(e,e&&e.isTTY))},stdout:o(i(!0,t.isatty(1))),stderr:o(i(!0,t.isatty(2)))}}();e&&(e.stderr||e).level>=2&&(t.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221])}catch(e){}t.inspectOpts=Object.keys(process.env).filter((e=>/^debug_/i.test(e))).reduce(((e,t)=>{const n=t.substring(6).toLowerCase().replace(/_([a-z])/g,((e,t)=>t.toUpperCase()));let r=process.env[t];return r=!!/^(yes|on|true|enabled)$/i.test(r)||!/^(no|off|false|disabled)$/i.test(r)&&("null"===r?null:Number(r)),e[n]=r,e}),{}),e.exports=Rr()(t);const{formatters:u}=e.exports;u.o=function(e){return this.inspectOpts.colors=this.useColors,r.inspect(e,this.inspectOpts).split("\n").map((e=>e.trim())).join(" ")},u.O=function(e){return this.inspectOpts.colors=this.useColors,r.inspect(e,this.inspectOpts)}}(Gr,Gr.exports)),Gr.exports}Jr=Nr,"undefined"==typeof process||"renderer"===process.type||!0===process.browser||process.__nwjs?Jr.exports=(Br||(Br=1,function(e,t){t.formatArgs=function(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+e.exports.humanize(this.diff),!this.useColors)return;const n="color: "+this.color;t.splice(1,0,n,"color: inherit");let r=0,u=0;t[0].replace(/%[a-zA-Z%]/g,(e=>{"%%"!==e&&(r++,"%c"===e&&(u=r))})),t.splice(u,0,n)},t.save=function(e){try{e?t.storage.setItem("debug",e):t.storage.removeItem("debug")}catch(e){}},t.load=function(){let e;try{e=t.storage.getItem("debug")}catch(e){}return!e&&"undefined"!=typeof process&&"env"in process&&(e=process.env.DEBUG),e},t.useColors=function(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type&&!window.process.__nwjs)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage=function(){try{return localStorage}catch(e){}}(),t.destroy=(()=>{let e=!1;return()=>{e||(e=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.log=console.debug||console.log||(()=>{}),e.exports=Rr()(t);const{formatters:n}=e.exports;n.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}}(Ir,Ir.exports)),Ir.exports):Jr.exports=Ur();var Wr=function(e){return(e=e||{}).circles?function(e){var t=[],n=[];return e.proto?function e(u){if("object"!=typeof u||null===u)return u;if(u instanceof Date)return new Date(u);if(Array.isArray(u))return r(u,e);if(u instanceof Map)return new Map(r(Array.from(u),e));if(u instanceof Set)return new Set(r(Array.from(u),e));var o={};for(var i in t.push(u),n.push(o),u){var s=u[i];if("object"!=typeof s||null===s)o[i]=s;else if(s instanceof Date)o[i]=new Date(s);else if(s instanceof Map)o[i]=new Map(r(Array.from(s),e));else if(s instanceof Set)o[i]=new Set(r(Array.from(s),e));else if(ArrayBuffer.isView(s))o[i]=zr(s);else{var c=t.indexOf(s);o[i]=-1!==c?n[c]:e(s)}}return t.pop(),n.pop(),o}:function e(u){if("object"!=typeof u||null===u)return u;if(u instanceof Date)return new Date(u);if(Array.isArray(u))return r(u,e);if(u instanceof Map)return new Map(r(Array.from(u),e));if(u instanceof Set)return new Set(r(Array.from(u),e));var o={};for(var i in t.push(u),n.push(o),u)if(!1!==Object.hasOwnProperty.call(u,i)){var s=u[i];if("object"!=typeof s||null===s)o[i]=s;else if(s instanceof Date)o[i]=new Date(s);else if(s instanceof Map)o[i]=new Map(r(Array.from(s),e));else if(s instanceof Set)o[i]=new Set(r(Array.from(s),e));else if(ArrayBuffer.isView(s))o[i]=zr(s);else{var c=t.indexOf(s);o[i]=-1!==c?n[c]:e(s)}}return t.pop(),n.pop(),o};function r(e,r){for(var u=Object.keys(e),o=new Array(u.length),i=0;i!e,Qr=e=>e&&"object"==typeof e&&!Array.isArray(e),eu=(e,t,n)=>{(Array.isArray(t)?t:[t]).forEach((t=>{if(t)throw new Error(`Problem with log4js configuration: (${Kr.inspect(e,{depth:5})}) - ${n}`)}))};var tu={configure:e=>{qr("New configuration to be validated: ",e),eu(e,Zr(Qr(e)),"must be an object."),qr(`Calling pre-processing listeners (${Yr.length})`),Yr.forEach((t=>t(e))),qr("Configuration pre-processing finished."),qr(`Calling configuration listeners (${Xr.length})`),Xr.forEach((t=>t(e))),qr("Configuration finished.")},addListener:e=>{Xr.push(e),qr(`Added listener, now ${Xr.length} listeners`)},addPreProcessingListener:e=>{Yr.push(e),qr(`Added pre-processing listener, now ${Yr.length} listeners`)},throwExceptionIf:eu,anObject:Qr,anInteger:e=>e&&"number"==typeof e&&Number.isInteger(e),validIdentifier:e=>/^[A-Za-z][A-Za-z0-9_]*$/g.test(e),not:Zr},nu={exports:{}};!function(e){function t(e,t){for(var n=e.toString();n.length-1?s:c,l=n(u.getHours()),f=n(u.getMinutes()),d=n(u.getSeconds()),D=t(u.getMilliseconds(),3),p=function(e){var t=Math.abs(e),n=String(Math.floor(t/60)),r=String(t%60);return n=("0"+n).slice(-2),r=("0"+r).slice(-2),0===e?"Z":(e<0?"+":"-")+n+":"+r}(u.getTimezoneOffset());return r.replace(/dd/g,o).replace(/MM/g,i).replace(/y{1,4}/g,a).replace(/hh/g,l).replace(/mm/g,f).replace(/ss/g,d).replace(/SSS/g,D).replace(/O/g,p)}function u(e,t,n,r){e["set"+(r?"":"UTC")+t](n)}e.exports=r,e.exports.asString=r,e.exports.parse=function(t,n,r){if(!t)throw new Error("pattern must be supplied");return function(t,n,r){var o=t.indexOf("O")<0,i=!1,s=[{pattern:/y{1,4}/,regexp:"\\d{1,4}",fn:function(e,t){u(e,"FullYear",t,o)}},{pattern:/MM/,regexp:"\\d{1,2}",fn:function(e,t){u(e,"Month",t-1,o),e.getMonth()!==t-1&&(i=!0)}},{pattern:/dd/,regexp:"\\d{1,2}",fn:function(e,t){i&&u(e,"Month",e.getMonth()-1,o),u(e,"Date",t,o)}},{pattern:/hh/,regexp:"\\d{1,2}",fn:function(e,t){u(e,"Hours",t,o)}},{pattern:/mm/,regexp:"\\d\\d",fn:function(e,t){u(e,"Minutes",t,o)}},{pattern:/ss/,regexp:"\\d\\d",fn:function(e,t){u(e,"Seconds",t,o)}},{pattern:/SSS/,regexp:"\\d\\d\\d",fn:function(e,t){u(e,"Milliseconds",t,o)}},{pattern:/O/,regexp:"[+-]\\d{1,2}:?\\d{2}?|Z",fn:function(e,t){t="Z"===t?0:t.replace(":","");var n=Math.abs(t),r=(t>0?-1:1)*(n%100+60*Math.floor(n/100));e.setUTCMinutes(e.getUTCMinutes()+r)}}],c=s.reduce((function(e,t){return t.pattern.test(e.regexp)?(t.index=e.regexp.match(t.pattern).index,e.regexp=e.regexp.replace(t.pattern,"("+t.regexp+")")):t.index=-1,e}),{regexp:t,index:[]}),a=s.filter((function(e){return e.index>-1}));a.sort((function(e,t){return e.index-t.index}));var l=new RegExp(c.regexp).exec(n);if(l){var f=r||e.exports.now();return a.forEach((function(e,t){e.fn(f,l[t+1])})),f}throw new Error("String '"+n+"' could not be parsed as '"+t+"'")}(t,n,r)},e.exports.now=function(){return new Date},e.exports.ISO8601_FORMAT="yyyy-MM-ddThh:mm:ss.SSS",e.exports.ISO8601_WITH_TZ_OFFSET_FORMAT="yyyy-MM-ddThh:mm:ss.SSSO",e.exports.DATETIME_FORMAT="dd MM yyyy hh:mm:ss.SSS",e.exports.ABSOLUTETIME_FORMAT="hh:mm:ss.SSS"}(nu);const ru=nu.exports,uu=E.default,ou=F.default,iu=p.default,su={bold:[1,22],italic:[3,23],underline:[4,24],inverse:[7,27],white:[37,39],grey:[90,39],black:[90,39],blue:[34,39],cyan:[36,39],green:[32,39],magenta:[35,39],red:[91,39],yellow:[33,39]};function cu(e){return e?`[${su[e][0]}m`:""}function au(e){return e?`[${su[e][1]}m`:""}function lu(e,t){return n=ou.format("[%s] [%s] %s - ",ru.asString(e.startTime),e.level.toString(),e.categoryName),cu(r=t)+n+au(r);var n,r}function fu(e){return lu(e)+ou.format(...e.data)}function du(e){return lu(e,e.level.colour)+ou.format(...e.data)}function Du(e){return ou.format(...e.data)}function pu(e){return e.data[0]}function Eu(e,t){const n=/%(-?[0-9]+)?(\.?-?[0-9]+)?([[\]cdhmnprzxXyflos%])(\{([^}]+)\})?|([^%]+)/;function r(e){return e&&e.pid?e.pid.toString():process.pid.toString()}e=e||"%r %p %c - %m%n";const u={c:function(e,t){let n=e.categoryName;if(t){const e=parseInt(t,10),r=n.split(".");ee&&(n=r.slice(-e).join(iu.sep))}return n},l:function(e){return e.lineNumber?`${e.lineNumber}`:""},o:function(e){return e.columnNumber?`${e.columnNumber}`:""},s:function(e){return e.callStack||""}};function o(e,t,n){return u[e](t,n)}function i(e,t,n){let r=e;return r=function(e,t){let n;return e?(n=parseInt(e.substr(1),10),n>0?t.slice(0,n):t.slice(n)):t}(t,r),r=function(e,t){let n;if(e)if("-"===e.charAt(0))for(n=parseInt(e.substr(1),10);t.lengthDu,basic:()=>fu,colored:()=>du,coloured:()=>du,pattern:e=>Eu(e&&e.pattern,e&&e.tokens),dummy:()=>pu};var hu={basicLayout:fu,messagePassThroughLayout:Du,patternLayout:Eu,colouredLayout:du,coloredLayout:du,dummyLayout:pu,addLayout(e,t){mu[e]=t},layout:(e,t)=>mu[e]&&mu[e](t)};const yu=tu,Cu=["white","grey","black","blue","cyan","green","magenta","red","yellow"];class Fu{constructor(e,t,n){this.level=e,this.levelStr=t,this.colour=n}toString(){return this.levelStr}static getLevel(e,t){return e?e instanceof Fu?e:(e instanceof Object&&e.levelStr&&(e=e.levelStr),Fu[e.toString().toUpperCase()]||t):t}static addLevels(e){if(e){Object.keys(e).forEach((t=>{const n=t.toUpperCase();Fu[n]=new Fu(e[t].value,n,e[t].colour);const r=Fu.levels.findIndex((e=>e.levelStr===n));r>-1?Fu.levels[r]=Fu[n]:Fu.levels.push(Fu[n])})),Fu.levels.sort(((e,t)=>e.level-t.level))}}isLessThanOrEqualTo(e){return"string"==typeof e&&(e=Fu.getLevel(e)),this.level<=e.level}isGreaterThanOrEqualTo(e){return"string"==typeof e&&(e=Fu.getLevel(e)),this.level>=e.level}isEqualTo(e){return"string"==typeof e&&(e=Fu.getLevel(e)),this.level===e.level}}Fu.levels=[],Fu.addLevels({ALL:{value:Number.MIN_VALUE,colour:"grey"},TRACE:{value:5e3,colour:"blue"},DEBUG:{value:1e4,colour:"cyan"},INFO:{value:2e4,colour:"green"},WARN:{value:3e4,colour:"yellow"},ERROR:{value:4e4,colour:"red"},FATAL:{value:5e4,colour:"magenta"},MARK:{value:9007199254740992,colour:"grey"},OFF:{value:Number.MAX_VALUE,colour:"grey"}}),yu.addListener((e=>{const t=e.levels;if(t){yu.throwExceptionIf(e,yu.not(yu.anObject(t)),"levels must be an object");Object.keys(t).forEach((n=>{yu.throwExceptionIf(e,yu.not(yu.validIdentifier(n)),`level name "${n}" is not a valid identifier (must start with a letter, only contain A-Z,a-z,0-9,_)`),yu.throwExceptionIf(e,yu.not(yu.anObject(t[n])),`level "${n}" must be an object`),yu.throwExceptionIf(e,yu.not(t[n].value),`level "${n}" must have a 'value' property`),yu.throwExceptionIf(e,yu.not(yu.anInteger(t[n].value)),`level "${n}".value must have an integer value`),yu.throwExceptionIf(e,yu.not(t[n].colour),`level "${n}" must have a 'colour' property`),yu.throwExceptionIf(e,yu.not(Cu.indexOf(t[n].colour)>-1),`level "${n}".colour must be one of ${Cu.join(", ")}`)}))}})),yu.addListener((e=>{Fu.addLevels(e.levels)}));var gu=Fu,Au={exports:{}},vu={};/*! (c) 2020 Andrea Giammarchi */ +const{parse:Su,stringify:wu}=JSON,{keys:Ou}=Object,bu=String,_u="string",Bu={},Pu="object",ku=(e,t)=>t,xu=e=>e instanceof bu?bu(e):e,Nu=(e,t)=>typeof t===_u?new bu(t):t,Iu=(e,t,n,r)=>{const u=[];for(let o=Ou(n),{length:i}=o,s=0;s{const r=bu(t.push(n)-1);return e.set(n,r),r},Ru=(e,t)=>{const n=Su(e,Nu).map(xu),r=n[0],u=t||ku,o=typeof r===Pu&&r?Iu(n,new Set,r,u):r;return u.call({"":o},"",o)};vu.parse=Ru;const Mu=(e,t,n)=>{const r=t&&typeof t===Pu?(e,n)=>""===e||-1Su(Mu(e));vu.fromJSON=e=>Ru(wu(e));const Lu=vu,ju=gu;class $u{constructor(e,t,n,r,u){this.startTime=new Date,this.categoryName=e,this.data=n,this.level=t,this.context=Object.assign({},r),this.pid=process.pid,u&&(this.functionName=u.functionName,this.fileName=u.fileName,this.lineNumber=u.lineNumber,this.columnNumber=u.columnNumber,this.callStack=u.callStack)}serialise(){const e=this.data.map((e=>(e&&e.message&&e.stack&&(e=Object.assign({message:e.message,stack:e.stack},e)),e)));return this.data=e,Lu.stringify(this)}static deserialise(e){let t;try{const n=Lu.parse(e);n.data=n.data.map((e=>{if(e&&e.message&&e.stack){const t=new Error(e);Object.keys(e).forEach((n=>{t[n]=e[n]})),e=t}return e})),t=new $u(n.categoryName,ju.getLevel(n.level.levelStr),n.data,n.context),t.startTime=new Date(n.startTime),t.pid=n.pid,t.cluster=n.cluster}catch(n){t=new $u("log4js",ju.ERROR,["Unable to parse log:",e,"because: ",n])}return t}}var Hu=$u;const Ju=Nr.exports("log4js:clustering"),Gu=Hu,Vu=tu;let Uu=!1,Wu=null;try{Wu=require("cluster")}catch(e){Ju("cluster module not present"),Uu=!0}const zu=[];let Ku=!1,qu="NODE_APP_INSTANCE";const Yu=()=>Ku&&"0"===process.env[qu],Xu=()=>Uu||Wu.isMaster||Yu(),Zu=e=>{zu.forEach((t=>t(e)))},Qu=(e,t)=>{if(Ju("cluster message received from worker ",e,": ",t),e.topic&&e.data&&(t=e,e=void 0),t&&t.topic&&"log4js:message"===t.topic){Ju("received message: ",t.data);const e=Gu.deserialise(t.data);Zu(e)}};Uu||Vu.addListener((e=>{zu.length=0,({pm2:Ku,disableClustering:Uu,pm2InstanceVar:qu="NODE_APP_INSTANCE"}=e),Ju(`clustering disabled ? ${Uu}`),Ju(`cluster.isMaster ? ${Wu&&Wu.isMaster}`),Ju(`pm2 enabled ? ${Ku}`),Ju(`pm2InstanceVar = ${qu}`),Ju(`process.env[${qu}] = ${process.env[qu]}`),Ku&&process.removeListener("message",Qu),Wu&&Wu.removeListener&&Wu.removeListener("message",Qu),Uu||e.disableClustering?Ju("Not listening for cluster messages, because clustering disabled."):Yu()?(Ju("listening for PM2 broadcast messages"),process.on("message",Qu)):Wu.isMaster?(Ju("listening for cluster messages"),Wu.on("message",Qu)):Ju("not listening for messages, because we are not a master process")}));var eo={onlyOnMaster:(e,t)=>Xu()?e():t,isMaster:Xu,send:e=>{Xu()?Zu(e):(Ku||(e.cluster={workerId:Wu.worker.id,worker:process.pid}),process.send({topic:"log4js:message",data:e.serialise()}))},onMessage:e=>{zu.push(e)}},to={};function no(e){if("number"==typeof e&&Number.isInteger(e))return e;const t={K:1024,M:1048576,G:1073741824},n=Object.keys(t),r=e.substr(e.length-1).toLocaleUpperCase(),u=e.substring(0,e.length-1).trim();if(n.indexOf(r)<0||!Number.isInteger(Number(u)))throw Error(`maxLogSize: "${e}" is invalid`);return u*t[r]}function ro(e){return function(e,t){const n=Object.assign({},t);return Object.keys(e).forEach((r=>{n[r]&&(n[r]=e[r](t[r]))})),n}({maxLogSize:no},e)}const uo={file:ro,fileSync:ro};to.modifyConfig=e=>uo[e.type]?uo[e.type](e):e;var oo={};const io=console.log.bind(console);oo.configure=function(e,t){let n=t.colouredLayout;return e.layout&&(n=t.layout(e.layout.type,e.layout)),function(e,t){return n=>{io(e(n,t))}}(n,e.timezoneOffset)};var so={};so.configure=function(e,t){let n=t.colouredLayout;return e.layout&&(n=t.layout(e.layout.type,e.layout)),function(e,t){return n=>{process.stdout.write(`${e(n,t)}\n`)}}(n,e.timezoneOffset)};var co={};co.configure=function(e,t){let n=t.colouredLayout;return e.layout&&(n=t.layout(e.layout.type,e.layout)),function(e,t){return n=>{process.stderr.write(`${e(n,t)}\n`)}}(n,e.timezoneOffset)};var ao={};ao.configure=function(e,t,n,r){const u=n(e.appender);return function(e,t,n,r){const u=r.getLevel(e),o=r.getLevel(t,r.FATAL);return e=>{const t=e.level;t.isGreaterThanOrEqualTo(u)&&t.isLessThanOrEqualTo(o)&&n(e)}}(e.level,e.maxLevel,u,r)};var lo={};const fo=Nr.exports("log4js:categoryFilter");lo.configure=function(e,t,n){const r=n(e.appender);return function(e,t){return"string"==typeof e&&(e=[e]),n=>{fo(`Checking ${n.categoryName} against ${e}`),-1===e.indexOf(n.categoryName)&&(fo("Not excluded, sending to appender"),t(n))}}(e.exclude,r)};var Do={};const po=Nr.exports("log4js:noLogFilter");Do.configure=function(e,t,n){const r=n(e.appender);return function(e,t){return n=>{po(`Checking data: ${n.data} against filters: ${e}`),"string"==typeof e&&(e=[e]),e=e.filter((e=>null!=e&&""!==e));const r=new RegExp(e.join("|"),"i");(0===e.length||n.data.findIndex((e=>r.test(e)))<0)&&(po("Not excluded, sending to appender"),t(n))}}(e.exclude,r)};var Eo={},mo={exports:{}},ho={},yo={fromCallback:function(e){return Object.defineProperty((function(){if("function"!=typeof arguments[arguments.length-1])return new Promise(((t,n)=>{arguments[arguments.length]=(e,r)=>{if(e)return n(e);t(r)},arguments.length++,e.apply(this,arguments)}));e.apply(this,arguments)}),"name",{value:e.name})},fromPromise:function(e){return Object.defineProperty((function(){const t=arguments[arguments.length-1];if("function"!=typeof t)return e.apply(this,arguments);e.apply(this,arguments).then((e=>t(null,e)),t)}),"name",{value:e.name})}};!function(e){const t=yo.fromCallback,n=we,r=["access","appendFile","chmod","chown","close","copyFile","fchmod","fchown","fdatasync","fstat","fsync","ftruncate","futimes","lchown","lchmod","link","lstat","mkdir","mkdtemp","open","readFile","readdir","readlink","realpath","rename","rmdir","stat","symlink","truncate","unlink","utimes","writeFile"].filter((e=>"function"==typeof n[e]));Object.keys(n).forEach((t=>{"promises"!==t&&(e[t]=n[t])})),r.forEach((r=>{e[r]=t(n[r])})),e.exists=function(e,t){return"function"==typeof t?n.exists(e,t):new Promise((t=>n.exists(e,t)))},e.read=function(e,t,r,u,o,i){return"function"==typeof i?n.read(e,t,r,u,o,i):new Promise(((i,s)=>{n.read(e,t,r,u,o,((e,t,n)=>{if(e)return s(e);i({bytesRead:t,buffer:n})}))}))},e.write=function(e,t,...r){return"function"==typeof r[r.length-1]?n.write(e,t,...r):new Promise(((u,o)=>{n.write(e,t,...r,((e,t,n)=>{if(e)return o(e);u({bytesWritten:t,buffer:n})}))}))},"function"==typeof n.realpath.native&&(e.realpath.native=t(n.realpath.native))}(ho);const Co=p.default;function Fo(e){return(e=Co.normalize(Co.resolve(e)).split(Co.sep)).length>0?e[0]:null}const go=/[<>:"|?*]/;var Ao=function(e){const t=Fo(e);return e=e.replace(t,""),go.test(e)};const vo=we,So=p.default,wo=Ao,Oo=parseInt("0777",8);var bo=function e(t,n,r,u){if("function"==typeof n?(r=n,n={}):n&&"object"==typeof n||(n={mode:n}),"win32"===process.platform&&wo(t)){const e=new Error(t+" contains invalid WIN32 path characters.");return e.code="EINVAL",r(e)}let o=n.mode;const i=n.fs||vo;void 0===o&&(o=Oo&~process.umask()),u||(u=null),r=r||function(){},t=So.resolve(t),i.mkdir(t,o,(o=>{if(!o)return r(null,u=u||t);if("ENOENT"===o.code){if(So.dirname(t)===t)return r(o);e(So.dirname(t),n,((u,o)=>{u?r(u,o):e(t,n,r,o)}))}else i.stat(t,((e,t)=>{e||!t.isDirectory()?r(o,u):r(null,u)}))}))};const _o=we,Bo=p.default,Po=Ao,ko=parseInt("0777",8);var xo=function e(t,n,r){n&&"object"==typeof n||(n={mode:n});let u=n.mode;const o=n.fs||_o;if("win32"===process.platform&&Po(t)){const e=new Error(t+" contains invalid WIN32 path characters.");throw e.code="EINVAL",e}void 0===u&&(u=ko&~process.umask()),r||(r=null),t=Bo.resolve(t);try{o.mkdirSync(t,u),r=r||t}catch(u){if("ENOENT"===u.code){if(Bo.dirname(t)===t)throw u;r=e(Bo.dirname(t),n,r),e(t,n,r)}else{let e;try{e=o.statSync(t)}catch(e){throw u}if(!e.isDirectory())throw u}}return r};const No=(0,yo.fromCallback)(bo);var Io={mkdirs:No,mkdirsSync:xo,mkdirp:No,mkdirpSync:xo,ensureDir:No,ensureDirSync:xo};const To=we;E.default,p.default;var Ro=function(e,t,n,r){To.open(e,"r+",((e,u)=>{if(e)return r(e);To.futimes(u,t,n,(e=>{To.close(u,(t=>{r&&r(e||t)}))}))}))},Mo=function(e,t,n){const r=To.openSync(e,"r+");return To.futimesSync(r,t,n),To.closeSync(r)};const Lo=we,jo=p.default,$o=10,Ho=5,Jo=0,Go=process.versions.node.split("."),Vo=Number.parseInt(Go[0],10),Uo=Number.parseInt(Go[1],10),Wo=Number.parseInt(Go[2],10);function zo(){if(Vo>$o)return!0;if(Vo===$o){if(Uo>Ho)return!0;if(Uo===Ho&&Wo>=Jo)return!0}return!1}function Ko(e,t){const n=jo.resolve(e).split(jo.sep).filter((e=>e)),r=jo.resolve(t).split(jo.sep).filter((e=>e));return n.reduce(((e,t,n)=>e&&r[n]===t),!0)}function qo(e,t,n){return`Cannot ${n} '${e}' to a subdirectory of itself, '${t}'.`}var Yo,Xo,Zo={checkPaths:function(e,t,n,r){!function(e,t,n){zo()?Lo.stat(e,{bigint:!0},((e,r)=>{if(e)return n(e);Lo.stat(t,{bigint:!0},((e,t)=>e?"ENOENT"===e.code?n(null,{srcStat:r,destStat:null}):n(e):n(null,{srcStat:r,destStat:t})))})):Lo.stat(e,((e,r)=>{if(e)return n(e);Lo.stat(t,((e,t)=>e?"ENOENT"===e.code?n(null,{srcStat:r,destStat:null}):n(e):n(null,{srcStat:r,destStat:t})))}))}(e,t,((u,o)=>{if(u)return r(u);const{srcStat:i,destStat:s}=o;return s&&s.ino&&s.dev&&s.ino===i.ino&&s.dev===i.dev?r(new Error("Source and destination must not be the same.")):i.isDirectory()&&Ko(e,t)?r(new Error(qo(e,t,n))):r(null,{srcStat:i,destStat:s})}))},checkPathsSync:function(e,t,n){const{srcStat:r,destStat:u}=function(e,t){let n,r;n=zo()?Lo.statSync(e,{bigint:!0}):Lo.statSync(e);try{r=zo()?Lo.statSync(t,{bigint:!0}):Lo.statSync(t)}catch(e){if("ENOENT"===e.code)return{srcStat:n,destStat:null};throw e}return{srcStat:n,destStat:r}}(e,t);if(u&&u.ino&&u.dev&&u.ino===r.ino&&u.dev===r.dev)throw new Error("Source and destination must not be the same.");if(r.isDirectory()&&Ko(e,t))throw new Error(qo(e,t,n));return{srcStat:r,destStat:u}},checkParentPaths:function e(t,n,r,u,o){const i=jo.resolve(jo.dirname(t)),s=jo.resolve(jo.dirname(r));if(s===i||s===jo.parse(s).root)return o();zo()?Lo.stat(s,{bigint:!0},((i,c)=>i?"ENOENT"===i.code?o():o(i):c.ino&&c.dev&&c.ino===n.ino&&c.dev===n.dev?o(new Error(qo(t,r,u))):e(t,n,s,u,o))):Lo.stat(s,((i,c)=>i?"ENOENT"===i.code?o():o(i):c.ino&&c.dev&&c.ino===n.ino&&c.dev===n.dev?o(new Error(qo(t,r,u))):e(t,n,s,u,o)))},checkParentPathsSync:function e(t,n,r,u){const o=jo.resolve(jo.dirname(t)),i=jo.resolve(jo.dirname(r));if(i===o||i===jo.parse(i).root)return;let s;try{s=zo()?Lo.statSync(i,{bigint:!0}):Lo.statSync(i)}catch(e){if("ENOENT"===e.code)return;throw e}if(s.ino&&s.dev&&s.ino===n.ino&&s.dev===n.dev)throw new Error(qo(t,r,u));return e(t,n,i,u)},isSrcSubdir:Ko};const Qo=we,ei=p.default,ti=Io.mkdirsSync,ni=Mo,ri=Zo;function ui(e,t,n,r){if(!r.filter||r.filter(t,n))return function(e,t,n,r){const u=r.dereference?Qo.statSync:Qo.lstatSync,o=u(t);if(o.isDirectory())return function(e,t,n,r,u){if(!t)return function(e,t,n,r){return Qo.mkdirSync(n),ii(t,n,r),Qo.chmodSync(n,e.mode)}(e,n,r,u);if(t&&!t.isDirectory())throw new Error(`Cannot overwrite non-directory '${r}' with directory '${n}'.`);return ii(n,r,u)}(o,e,t,n,r);if(o.isFile()||o.isCharacterDevice()||o.isBlockDevice())return function(e,t,n,r,u){return t?function(e,t,n,r){if(r.overwrite)return Qo.unlinkSync(n),oi(e,t,n,r);if(r.errorOnExist)throw new Error(`'${n}' already exists`)}(e,n,r,u):oi(e,n,r,u)}(o,e,t,n,r);if(o.isSymbolicLink())return function(e,t,n,r){let u=Qo.readlinkSync(t);r.dereference&&(u=ei.resolve(process.cwd(),u));if(e){let e;try{e=Qo.readlinkSync(n)}catch(e){if("EINVAL"===e.code||"UNKNOWN"===e.code)return Qo.symlinkSync(u,n);throw e}if(r.dereference&&(e=ei.resolve(process.cwd(),e)),ri.isSrcSubdir(u,e))throw new Error(`Cannot copy '${u}' to a subdirectory of itself, '${e}'.`);if(Qo.statSync(n).isDirectory()&&ri.isSrcSubdir(e,u))throw new Error(`Cannot overwrite '${e}' with '${u}'.`);return function(e,t){return Qo.unlinkSync(t),Qo.symlinkSync(e,t)}(u,n)}return Qo.symlinkSync(u,n)}(e,t,n,r)}(e,t,n,r)}function oi(e,t,n,r){return"function"==typeof Qo.copyFileSync?(Qo.copyFileSync(t,n),Qo.chmodSync(n,e.mode),r.preserveTimestamps?ni(n,e.atime,e.mtime):void 0):function(e,t,n,r){const u=65536,o=(Xo?Yo:(Xo=1,Yo=function(e){if("function"==typeof Buffer.allocUnsafe)try{return Buffer.allocUnsafe(e)}catch(t){return new Buffer(e)}return new Buffer(e)}))(u),i=Qo.openSync(t,"r"),s=Qo.openSync(n,"w",e.mode);let c=0;for(;cfunction(e,t,n,r){const u=ei.join(t,e),o=ei.join(n,e),{destStat:i}=ri.checkPathsSync(u,o,"copy");return ui(i,u,o,r)}(r,e,t,n)))}var si=function(e,t,n){"function"==typeof n&&(n={filter:n}),(n=n||{}).clobber=!("clobber"in n)||!!n.clobber,n.overwrite="overwrite"in n?!!n.overwrite:n.clobber,n.preserveTimestamps&&"ia32"===process.arch&&console.warn("fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n\n see https://github.com/jprichardson/node-fs-extra/issues/269");const{srcStat:r,destStat:u}=ri.checkPathsSync(e,t,"copy");return ri.checkParentPathsSync(e,r,t,"copy"),function(e,t,n,r){if(r.filter&&!r.filter(t,n))return;const u=ei.dirname(n);Qo.existsSync(u)||ti(u);return ui(e,t,n,r)}(u,e,t,n)},ci={copySync:si};const ai=yo.fromPromise,li=ho;var fi={pathExists:ai((function(e){return li.access(e).then((()=>!0)).catch((()=>!1))})),pathExistsSync:li.existsSync};const di=we,Di=p.default,pi=Io.mkdirs,Ei=fi.pathExists,mi=Ro,hi=Zo;function yi(e,t,n,r,u){const o=Di.dirname(n);Ei(o,((i,s)=>i?u(i):s?Fi(e,t,n,r,u):void pi(o,(o=>o?u(o):Fi(e,t,n,r,u)))))}function Ci(e,t,n,r,u,o){Promise.resolve(u.filter(n,r)).then((i=>i?e(t,n,r,u,o):o()),(e=>o(e)))}function Fi(e,t,n,r,u){return r.filter?Ci(gi,e,t,n,r,u):gi(e,t,n,r,u)}function gi(e,t,n,r,u){(r.dereference?di.stat:di.lstat)(t,((o,i)=>o?u(o):i.isDirectory()?function(e,t,n,r,u,o){if(!t)return function(e,t,n,r,u){di.mkdir(n,(o=>{if(o)return u(o);Si(t,n,r,(t=>t?u(t):di.chmod(n,e.mode,u)))}))}(e,n,r,u,o);if(t&&!t.isDirectory())return o(new Error(`Cannot overwrite non-directory '${r}' with directory '${n}'.`));return Si(n,r,u,o)}(i,e,t,n,r,u):i.isFile()||i.isCharacterDevice()||i.isBlockDevice()?function(e,t,n,r,u,o){return t?function(e,t,n,r,u){if(!r.overwrite)return r.errorOnExist?u(new Error(`'${n}' already exists`)):u();di.unlink(n,(o=>o?u(o):Ai(e,t,n,r,u)))}(e,n,r,u,o):Ai(e,n,r,u,o)}(i,e,t,n,r,u):i.isSymbolicLink()?function(e,t,n,r,u){di.readlink(t,((t,o)=>t?u(t):(r.dereference&&(o=Di.resolve(process.cwd(),o)),e?void di.readlink(n,((t,i)=>t?"EINVAL"===t.code||"UNKNOWN"===t.code?di.symlink(o,n,u):u(t):(r.dereference&&(i=Di.resolve(process.cwd(),i)),hi.isSrcSubdir(o,i)?u(new Error(`Cannot copy '${o}' to a subdirectory of itself, '${i}'.`)):e.isDirectory()&&hi.isSrcSubdir(i,o)?u(new Error(`Cannot overwrite '${i}' with '${o}'.`)):function(e,t,n){di.unlink(t,(r=>r?n(r):di.symlink(e,t,n)))}(o,n,u)))):di.symlink(o,n,u))))}(e,t,n,r,u):void 0))}function Ai(e,t,n,r,u){return"function"==typeof di.copyFile?di.copyFile(t,n,(t=>t?u(t):vi(e,n,r,u))):function(e,t,n,r,u){const o=di.createReadStream(t);o.on("error",(e=>u(e))).once("open",(()=>{const t=di.createWriteStream(n,{mode:e.mode});t.on("error",(e=>u(e))).on("open",(()=>o.pipe(t))).once("close",(()=>vi(e,n,r,u)))}))}(e,t,n,r,u)}function vi(e,t,n,r){di.chmod(t,e.mode,(u=>u?r(u):n.preserveTimestamps?mi(t,e.atime,e.mtime,r):r()))}function Si(e,t,n,r){di.readdir(e,((u,o)=>u?r(u):wi(o,e,t,n,r)))}function wi(e,t,n,r,u){const o=e.pop();return o?function(e,t,n,r,u,o){const i=Di.join(n,t),s=Di.join(r,t);hi.checkPaths(i,s,"copy",((t,c)=>{if(t)return o(t);const{destStat:a}=c;Fi(a,i,s,u,(t=>t?o(t):wi(e,n,r,u,o)))}))}(e,o,t,n,r,u):u()}var Oi=function(e,t,n,r){"function"!=typeof n||r?"function"==typeof n&&(n={filter:n}):(r=n,n={}),r=r||function(){},(n=n||{}).clobber=!("clobber"in n)||!!n.clobber,n.overwrite="overwrite"in n?!!n.overwrite:n.clobber,n.preserveTimestamps&&"ia32"===process.arch&&console.warn("fs-extra: Using the preserveTimestamps option in 32-bit node is not recommended;\n\n see https://github.com/jprichardson/node-fs-extra/issues/269"),hi.checkPaths(e,t,"copy",((u,o)=>{if(u)return r(u);const{srcStat:i,destStat:s}=o;hi.checkParentPaths(e,i,t,"copy",(u=>u?r(u):n.filter?Ci(yi,s,e,t,n,r):yi(s,e,t,n,r)))}))};var bi={copy:(0,yo.fromCallback)(Oi)};const _i=we,Bi=p.default,Pi=g.default,ki="win32"===process.platform;function xi(e){["unlink","chmod","stat","lstat","rmdir","readdir"].forEach((t=>{e[t]=e[t]||_i[t],e[t+="Sync"]=e[t]||_i[t]})),e.maxBusyTries=e.maxBusyTries||3}function Ni(e,t,n){let r=0;"function"==typeof t&&(n=t,t={}),Pi(e,"rimraf: missing path"),Pi.strictEqual(typeof e,"string","rimraf: path should be a string"),Pi.strictEqual(typeof n,"function","rimraf: callback function required"),Pi(t,"rimraf: invalid options argument provided"),Pi.strictEqual(typeof t,"object","rimraf: options should be object"),xi(t),Ii(e,t,(function u(o){if(o){if(("EBUSY"===o.code||"ENOTEMPTY"===o.code||"EPERM"===o.code)&&rIi(e,t,u)),100*r)}"ENOENT"===o.code&&(o=null)}n(o)}))}function Ii(e,t,n){Pi(e),Pi(t),Pi("function"==typeof n),t.lstat(e,((r,u)=>r&&"ENOENT"===r.code?n(null):r&&"EPERM"===r.code&&ki?Ti(e,t,r,n):u&&u.isDirectory()?Mi(e,t,r,n):void t.unlink(e,(r=>{if(r){if("ENOENT"===r.code)return n(null);if("EPERM"===r.code)return ki?Ti(e,t,r,n):Mi(e,t,r,n);if("EISDIR"===r.code)return Mi(e,t,r,n)}return n(r)}))))}function Ti(e,t,n,r){Pi(e),Pi(t),Pi("function"==typeof r),n&&Pi(n instanceof Error),t.chmod(e,438,(u=>{u?r("ENOENT"===u.code?null:n):t.stat(e,((u,o)=>{u?r("ENOENT"===u.code?null:n):o.isDirectory()?Mi(e,t,n,r):t.unlink(e,r)}))}))}function Ri(e,t,n){let r;Pi(e),Pi(t),n&&Pi(n instanceof Error);try{t.chmodSync(e,438)}catch(e){if("ENOENT"===e.code)return;throw n}try{r=t.statSync(e)}catch(e){if("ENOENT"===e.code)return;throw n}r.isDirectory()?ji(e,t,n):t.unlinkSync(e)}function Mi(e,t,n,r){Pi(e),Pi(t),n&&Pi(n instanceof Error),Pi("function"==typeof r),t.rmdir(e,(u=>{!u||"ENOTEMPTY"!==u.code&&"EEXIST"!==u.code&&"EPERM"!==u.code?u&&"ENOTDIR"===u.code?r(n):r(u):function(e,t,n){Pi(e),Pi(t),Pi("function"==typeof n),t.readdir(e,((r,u)=>{if(r)return n(r);let o,i=u.length;if(0===i)return t.rmdir(e,n);u.forEach((r=>{Ni(Bi.join(e,r),t,(r=>{if(!o)return r?n(o=r):void(0==--i&&t.rmdir(e,n))}))}))}))}(e,t,r)}))}function Li(e,t){let n;xi(t=t||{}),Pi(e,"rimraf: missing path"),Pi.strictEqual(typeof e,"string","rimraf: path should be a string"),Pi(t,"rimraf: missing options"),Pi.strictEqual(typeof t,"object","rimraf: options should be object");try{n=t.lstatSync(e)}catch(n){if("ENOENT"===n.code)return;"EPERM"===n.code&&ki&&Ri(e,t,n)}try{n&&n.isDirectory()?ji(e,t,null):t.unlinkSync(e)}catch(n){if("ENOENT"===n.code)return;if("EPERM"===n.code)return ki?Ri(e,t,n):ji(e,t,n);if("EISDIR"!==n.code)throw n;ji(e,t,n)}}function ji(e,t,n){Pi(e),Pi(t),n&&Pi(n instanceof Error);try{t.rmdirSync(e)}catch(r){if("ENOTDIR"===r.code)throw n;if("ENOTEMPTY"===r.code||"EEXIST"===r.code||"EPERM"===r.code)!function(e,t){if(Pi(e),Pi(t),t.readdirSync(e).forEach((n=>Li(Bi.join(e,n),t))),!ki){return t.rmdirSync(e,t)}{const n=Date.now();do{try{return t.rmdirSync(e,t)}catch(e){}}while(Date.now()-n<500)}}(e,t);else if("ENOENT"!==r.code)throw r}}var $i=Ni;Ni.sync=Li;const Hi=$i;var Ji={remove:(0,yo.fromCallback)(Hi),removeSync:Hi.sync};const Gi=yo.fromCallback,Vi=we,Ui=p.default,Wi=Io,zi=Ji,Ki=Gi((function(e,t){t=t||function(){},Vi.readdir(e,((n,r)=>{if(n)return Wi.mkdirs(e,t);r=r.map((t=>Ui.join(e,t))),function e(){const n=r.pop();if(!n)return t();zi.remove(n,(n=>{if(n)return t(n);e()}))}()}))}));function qi(e){let t;try{t=Vi.readdirSync(e)}catch(t){return Wi.mkdirsSync(e)}t.forEach((t=>{t=Ui.join(e,t),zi.removeSync(t)}))}var Yi={emptyDirSync:qi,emptydirSync:qi,emptyDir:Ki,emptydir:Ki};const Xi=yo.fromCallback,Zi=p.default,Qi=we,es=Io,ts=fi.pathExists;var ns={createFile:Xi((function(e,t){function n(){Qi.writeFile(e,"",(e=>{if(e)return t(e);t()}))}Qi.stat(e,((r,u)=>{if(!r&&u.isFile())return t();const o=Zi.dirname(e);ts(o,((e,r)=>e?t(e):r?n():void es.mkdirs(o,(e=>{if(e)return t(e);n()}))))}))})),createFileSync:function(e){let t;try{t=Qi.statSync(e)}catch(e){}if(t&&t.isFile())return;const n=Zi.dirname(e);Qi.existsSync(n)||es.mkdirsSync(n),Qi.writeFileSync(e,"")}};const rs=yo.fromCallback,us=p.default,os=we,is=Io,ss=fi.pathExists;var cs={createLink:rs((function(e,t,n){function r(e,t){os.link(e,t,(e=>{if(e)return n(e);n(null)}))}ss(t,((u,o)=>u?n(u):o?n(null):void os.lstat(e,(u=>{if(u)return u.message=u.message.replace("lstat","ensureLink"),n(u);const o=us.dirname(t);ss(o,((u,i)=>u?n(u):i?r(e,t):void is.mkdirs(o,(u=>{if(u)return n(u);r(e,t)}))))}))))})),createLinkSync:function(e,t){if(os.existsSync(t))return;try{os.lstatSync(e)}catch(e){throw e.message=e.message.replace("lstat","ensureLink"),e}const n=us.dirname(t);return os.existsSync(n)||is.mkdirsSync(n),os.linkSync(e,t)}};const as=p.default,ls=we,fs=fi.pathExists;var ds={symlinkPaths:function(e,t,n){if(as.isAbsolute(e))return ls.lstat(e,(t=>t?(t.message=t.message.replace("lstat","ensureSymlink"),n(t)):n(null,{toCwd:e,toDst:e})));{const r=as.dirname(t),u=as.join(r,e);return fs(u,((t,o)=>t?n(t):o?n(null,{toCwd:u,toDst:e}):ls.lstat(e,(t=>t?(t.message=t.message.replace("lstat","ensureSymlink"),n(t)):n(null,{toCwd:e,toDst:as.relative(r,e)})))))}},symlinkPathsSync:function(e,t){let n;if(as.isAbsolute(e)){if(n=ls.existsSync(e),!n)throw new Error("absolute srcpath does not exist");return{toCwd:e,toDst:e}}{const r=as.dirname(t),u=as.join(r,e);if(n=ls.existsSync(u),n)return{toCwd:u,toDst:e};if(n=ls.existsSync(e),!n)throw new Error("relative srcpath does not exist");return{toCwd:e,toDst:as.relative(r,e)}}}};const Ds=we;var ps={symlinkType:function(e,t,n){if(n="function"==typeof t?t:n,t="function"!=typeof t&&t)return n(null,t);Ds.lstat(e,((e,r)=>{if(e)return n(null,"file");t=r&&r.isDirectory()?"dir":"file",n(null,t)}))},symlinkTypeSync:function(e,t){let n;if(t)return t;try{n=Ds.lstatSync(e)}catch(e){return"file"}return n&&n.isDirectory()?"dir":"file"}};const Es=yo.fromCallback,ms=p.default,hs=we,ys=Io.mkdirs,Cs=Io.mkdirsSync,Fs=ds.symlinkPaths,gs=ds.symlinkPathsSync,As=ps.symlinkType,vs=ps.symlinkTypeSync,Ss=fi.pathExists;var ws={createSymlink:Es((function(e,t,n,r){r="function"==typeof n?n:r,n="function"!=typeof n&&n,Ss(t,((u,o)=>u?r(u):o?r(null):void Fs(e,t,((u,o)=>{if(u)return r(u);e=o.toDst,As(o.toCwd,n,((n,u)=>{if(n)return r(n);const o=ms.dirname(t);Ss(o,((n,i)=>n?r(n):i?hs.symlink(e,t,u,r):void ys(o,(n=>{if(n)return r(n);hs.symlink(e,t,u,r)}))))}))}))))})),createSymlinkSync:function(e,t,n){if(hs.existsSync(t))return;const r=gs(e,t);e=r.toDst,n=vs(r.toCwd,n);const u=ms.dirname(t);return hs.existsSync(u)||Cs(u),hs.symlinkSync(e,t,n)}};var Os,bs={createFile:ns.createFile,createFileSync:ns.createFileSync,ensureFile:ns.createFile,ensureFileSync:ns.createFileSync,createLink:cs.createLink,createLinkSync:cs.createLinkSync,ensureLink:cs.createLink,ensureLinkSync:cs.createLinkSync,createSymlink:ws.createSymlink,createSymlinkSync:ws.createSymlinkSync,ensureSymlink:ws.createSymlink,ensureSymlinkSync:ws.createSymlinkSync};try{Os=we}catch(e){Os=D.default}function _s(e,t){var n,r="\n";return"object"==typeof t&&null!==t&&(t.spaces&&(n=t.spaces),t.EOL&&(r=t.EOL)),JSON.stringify(e,t?t.replacer:null,n).replace(/\n/g,r)+r}function Bs(e){return Buffer.isBuffer(e)&&(e=e.toString("utf8")),e=e.replace(/^\uFEFF/,"")}var Ps={readFile:function(e,t,n){null==n&&(n=t,t={}),"string"==typeof t&&(t={encoding:t});var r=(t=t||{}).fs||Os,u=!0;"throws"in t&&(u=t.throws),r.readFile(e,t,(function(r,o){if(r)return n(r);var i;o=Bs(o);try{i=JSON.parse(o,t?t.reviver:null)}catch(t){return u?(t.message=e+": "+t.message,n(t)):n(null,null)}n(null,i)}))},readFileSync:function(e,t){"string"==typeof(t=t||{})&&(t={encoding:t});var n=t.fs||Os,r=!0;"throws"in t&&(r=t.throws);try{var u=n.readFileSync(e,t);return u=Bs(u),JSON.parse(u,t.reviver)}catch(t){if(r)throw t.message=e+": "+t.message,t;return null}},writeFile:function(e,t,n,r){null==r&&(r=n,n={});var u=(n=n||{}).fs||Os,o="";try{o=_s(t,n)}catch(e){return void(r&&r(e,null))}u.writeFile(e,o,n,r)},writeFileSync:function(e,t,n){var r=(n=n||{}).fs||Os,u=_s(t,n);return r.writeFileSync(e,u,n)}},ks=Ps;const xs=yo.fromCallback,Ns=ks;var Is={readJson:xs(Ns.readFile),readJsonSync:Ns.readFileSync,writeJson:xs(Ns.writeFile),writeJsonSync:Ns.writeFileSync};const Ts=p.default,Rs=Io,Ms=fi.pathExists,Ls=Is;var js=function(e,t,n,r){"function"==typeof n&&(r=n,n={});const u=Ts.dirname(e);Ms(u,((o,i)=>o?r(o):i?Ls.writeJson(e,t,n,r):void Rs.mkdirs(u,(u=>{if(u)return r(u);Ls.writeJson(e,t,n,r)}))))};const $s=we,Hs=p.default,Js=Io,Gs=Is;var Vs=function(e,t,n){const r=Hs.dirname(e);$s.existsSync(r)||Js.mkdirsSync(r),Gs.writeJsonSync(e,t,n)};const Us=yo.fromCallback,Ws=Is;Ws.outputJson=Us(js),Ws.outputJsonSync=Vs,Ws.outputJSON=Ws.outputJson,Ws.outputJSONSync=Ws.outputJsonSync,Ws.writeJSON=Ws.writeJson,Ws.writeJSONSync=Ws.writeJsonSync,Ws.readJSON=Ws.readJson,Ws.readJSONSync=Ws.readJsonSync;var zs=Ws;const Ks=we,qs=p.default,Ys=ci.copySync,Xs=Ji.removeSync,Zs=Io.mkdirpSync,Qs=Zo;function ec(e,t,n){try{Ks.renameSync(e,t)}catch(r){if("EXDEV"!==r.code)throw r;return function(e,t,n){const r={overwrite:n,errorOnExist:!0};return Ys(e,t,r),Xs(e)}(e,t,n)}}var tc=function(e,t,n){const r=(n=n||{}).overwrite||n.clobber||!1,{srcStat:u}=Qs.checkPathsSync(e,t,"move");return Qs.checkParentPathsSync(e,u,t,"move"),Zs(qs.dirname(t)),function(e,t,n){if(n)return Xs(t),ec(e,t,n);if(Ks.existsSync(t))throw new Error("dest already exists.");return ec(e,t,n)}(e,t,r)},nc={moveSync:tc};const rc=we,uc=p.default,oc=bi.copy,ic=Ji.remove,sc=Io.mkdirp,cc=fi.pathExists,ac=Zo;function lc(e,t,n,r){rc.rename(e,t,(u=>u?"EXDEV"!==u.code?r(u):function(e,t,n,r){const u={overwrite:n,errorOnExist:!0};oc(e,t,u,(t=>t?r(t):ic(e,r)))}(e,t,n,r):r()))}var fc=function(e,t,n,r){"function"==typeof n&&(r=n,n={});const u=n.overwrite||n.clobber||!1;ac.checkPaths(e,t,"move",((n,o)=>{if(n)return r(n);const{srcStat:i}=o;ac.checkParentPaths(e,i,t,"move",(n=>{if(n)return r(n);sc(uc.dirname(t),(n=>n?r(n):function(e,t,n,r){if(n)return ic(t,(u=>u?r(u):lc(e,t,n,r)));cc(t,((u,o)=>u?r(u):o?r(new Error("dest already exists.")):lc(e,t,n,r)))}(e,t,u,r)))}))}))};var dc={move:(0,yo.fromCallback)(fc)};const Dc=yo.fromCallback,pc=we,Ec=p.default,mc=Io,hc=fi.pathExists;var yc={outputFile:Dc((function(e,t,n,r){"function"==typeof n&&(r=n,n="utf8");const u=Ec.dirname(e);hc(u,((o,i)=>o?r(o):i?pc.writeFile(e,t,n,r):void mc.mkdirs(u,(u=>{if(u)return r(u);pc.writeFile(e,t,n,r)}))))})),outputFileSync:function(e,...t){const n=Ec.dirname(e);if(pc.existsSync(n))return pc.writeFileSync(e,...t);mc.mkdirsSync(n),pc.writeFileSync(e,...t)}};!function(e){e.exports=Object.assign({},ho,ci,bi,Yi,bs,zs,Io,nc,dc,yc,fi,Ji);const t=D.default;Object.getOwnPropertyDescriptor(t,"promises")&&Object.defineProperty(e.exports,"promises",{get:()=>t.promises})}(mo);const Cc=Nr.exports("streamroller:fileNameFormatter"),Fc=p.default;const gc=Nr.exports("streamroller:fileNameParser"),Ac=nu.exports;const vc=Nr.exports("streamroller:moveAndMaybeCompressFile"),Sc=mo.exports,wc=v.default;var Oc=async(e,t,n)=>{if(n=function(e){const t={mode:parseInt("0600",8),compress:!1},n=Object.assign({},t,e);return vc(`_parseOption: moveAndMaybeCompressFile called with option=${JSON.stringify(n)}`),n}(n),e!==t){if(await Sc.pathExists(e))if(vc(`moveAndMaybeCompressFile: moving file from ${e} to ${t} ${n.compress?"with":"without"} compress`),n.compress)await new Promise(((r,u)=>{let o=!1;const i=Sc.createWriteStream(t,{mode:n.mode,flags:"wx"}).on("open",(()=>{o=!0;const t=Sc.createReadStream(e).on("open",(()=>{t.pipe(wc.createGzip()).pipe(i)})).on("error",(t=>{vc(`moveAndMaybeCompressFile: error reading ${e}`,t),i.destroy(t)}))})).on("finish",(()=>{vc(`moveAndMaybeCompressFile: finished compressing ${t}, deleting ${e}`),Sc.unlink(e).then(r).catch((t=>{vc(`moveAndMaybeCompressFile: error deleting ${e}, truncating instead`,t),Sc.truncate(e).then(r).catch((t=>{vc(`moveAndMaybeCompressFile: error truncating ${e}`,t),u(t)}))}))})).on("error",(e=>{o?(vc(`moveAndMaybeCompressFile: error writing ${t}, deleting`,e),Sc.unlink(t).then((()=>{u(e)})).catch((e=>{vc(`moveAndMaybeCompressFile: error deleting ${t}`,e),u(e)}))):(vc(`moveAndMaybeCompressFile: error creating ${t}`,e),u(e))}))})).catch((()=>{}));else{vc(`moveAndMaybeCompressFile: renaming ${e} to ${t}`);try{await Sc.move(e,t,{overwrite:!0})}catch(n){if(vc(`moveAndMaybeCompressFile: error renaming ${e} to ${t}`,n),"ENOENT"!==n.code){vc("moveAndMaybeCompressFile: trying copy+truncate instead");try{await Sc.copy(e,t,{overwrite:!0}),await Sc.truncate(e)}catch(e){vc("moveAndMaybeCompressFile: error copy+truncate",e)}}}}}else vc("moveAndMaybeCompressFile: source and target are the same, not doing anything")};const bc=Nr.exports("streamroller:RollingFileWriteStream"),_c=mo.exports,Bc=p.default,Pc=E.default,kc=()=>new Date,xc=nu.exports,{Writable:Nc}=C.default,Ic=({file:e,keepFileExt:t,needsIndex:n,alwaysIncludeDate:r,compress:u,fileNameSep:o})=>{let i=o||".";const s=Fc.join(e.dir,e.name),c=t=>t+e.ext,a=(e,t,r)=>!n&&r||!t?e:e+i+t,l=(e,t,n)=>(t>0||r)&&n?e+i+n:e,f=(e,t)=>t&&u?e+".gz":e,d=t?[l,a,c,f]:[c,l,a,f];return({date:e,index:t})=>(Cc(`_formatFileName: date=${e}, index=${t}`),d.reduce(((n,r)=>r(n,t,e)),s))},Tc=({file:e,keepFileExt:t,pattern:n,fileNameSep:r})=>{let u=r||".";const o="__NOT_MATCHING__";let i=[(e,t)=>e.endsWith(".gz")?(gc("it is gzipped"),t.isCompressed=!0,e.slice(0,-1*".gz".length)):e,t?t=>t.startsWith(e.name)&&t.endsWith(e.ext)?(gc("it starts and ends with the right things"),t.slice(e.name.length+1,-1*e.ext.length)):o:t=>t.startsWith(e.base)?(gc("it starts with the right things"),t.slice(e.base.length+1)):o,n?(e,t)=>{const r=e.split(u);let o=r[r.length-1];gc("items: ",r,", indexStr: ",o);let i=e;void 0!==o&&o.match(/^\d+$/)?(i=e.slice(0,-1*(o.length+1)),gc(`dateStr is ${i}`),n&&!i&&(i=o,o="0")):o="0";try{const r=Ac.parse(n,i,new Date(0,0));return Ac.asString(n,r)!==i?e:(t.index=parseInt(o,10),t.date=i,t.timestamp=r.getTime(),"")}catch(t){return gc(`Problem parsing ${i} as ${n}, error was: `,t),e}}:(e,t)=>e.match(/^\d+$/)?(gc("it has an index"),t.index=parseInt(e,10),""):e];return e=>{let t={filename:e,index:0,isCompressed:!1};return i.reduce(((e,n)=>n(e,t)),e)?null:t}},Rc=Oc;var Mc=class extends Nc{constructor(e,t){if(bc(`constructor: creating RollingFileWriteStream. path=${e}`),"string"!=typeof e||0===e.length)throw new Error(`Invalid filename: ${e}`);if(e.endsWith(Bc.sep))throw new Error(`Filename is a directory: ${e}`);0===e.indexOf(`~${Bc.sep}`)&&(e=e.replace("~",Pc.homedir())),super(t),this.options=this._parseOption(t),this.fileObject=Bc.parse(e),""===this.fileObject.dir&&(this.fileObject=Bc.parse(Bc.join(process.cwd(),e))),this.fileFormatter=Ic({file:this.fileObject,alwaysIncludeDate:this.options.alwaysIncludePattern,needsIndex:this.options.maxSize 0`)}else delete n.maxSize;if(n.numBackups||0===n.numBackups){if(n.numBackups<0)throw new Error(`options.numBackups (${n.numBackups}) should be >= 0`);if(n.numBackups>=Number.MAX_SAFE_INTEGER)throw new Error(`options.numBackups (${n.numBackups}) should be < Number.MAX_SAFE_INTEGER`);n.numToKeep=n.numBackups+1}else if(n.numToKeep<=0)throw new Error(`options.numToKeep (${n.numToKeep}) should be > 0`);return bc(`_parseOption: creating stream with option=${JSON.stringify(n)}`),n}_final(e){this.currentFileStream.end("",this.options.encoding,e)}_write(e,t,n){this._shouldRoll().then((()=>{bc(`_write: writing chunk. file=${this.currentFileStream.path} state=${JSON.stringify(this.state)} chunk=${e}`),this.currentFileStream.write(e,t,(t=>{this.state.currentSize+=e.length,n(t)}))}))}async _shouldRoll(){(this._dateChanged()||this._tooBig())&&(bc(`_shouldRoll: rolling because dateChanged? ${this._dateChanged()} or tooBig? ${this._tooBig()}`),await this._roll())}_dateChanged(){return this.state.currentDate&&this.state.currentDate!==xc(this.options.pattern,kc())}_tooBig(){return this.state.currentSize>=this.options.maxSize}_roll(){return bc("_roll: closing the current stream"),new Promise(((e,t)=>{this.currentFileStream.end("",this.options.encoding,(()=>{this._moveOldFiles().then(e).catch(t)}))}))}async _moveOldFiles(){const e=await this._getExistingFiles();for(let t=(this.state.currentDate?e.filter((e=>e.date===this.state.currentDate)):e).length;t>=0;t--){bc(`_moveOldFiles: i = ${t}`);const e=this.fileFormatter({date:this.state.currentDate,index:t}),n=this.fileFormatter({date:this.state.currentDate,index:t+1}),r={compress:this.options.compress&&0===t,mode:this.options.mode};await Rc(e,n,r)}this.state.currentSize=0,this.state.currentDate=this.state.currentDate?xc(this.options.pattern,kc()):null,bc(`_moveOldFiles: finished rolling files. state=${JSON.stringify(this.state)}`),this._renewWriteStream(),await new Promise(((e,t)=>{this.currentFileStream.write("","utf8",(()=>{this._clean().then(e).catch(t)}))}))}async _getExistingFiles(){const e=await _c.readdir(this.fileObject.dir).catch((()=>[]));bc(`_getExistingFiles: files=${e}`);const t=e.map((e=>this.fileNameParser(e))).filter((e=>e)),n=e=>(e.timestamp?e.timestamp:kc().getTime())-e.index;return t.sort(((e,t)=>n(e)-n(t))),t}_renewWriteStream(){const e=this.fileFormatter({date:this.state.currentDate,index:0}),t=e=>{try{return _c.mkdirSync(e,{recursive:!0})}catch(n){if("ENOENT"===n.code)return t(Bc.dirname(e)),t(e);if("EEXIST"!==n.code&&"EROFS"!==n.code)throw n;try{if(_c.statSync(e).isDirectory())return e;throw n}catch(e){throw n}}};t(this.fileObject.dir);const n={flags:this.options.flags,encoding:this.options.encoding,mode:this.options.mode};var r,u;_c.appendFileSync(e,"",(r={...n},u="flags",r["flag"]=r[u],delete r[u],r)),this.currentFileStream=_c.createWriteStream(e,n),this.currentFileStream.on("error",(e=>{this.emit("error",e)}))}async _clean(){const e=await this._getExistingFiles();if(bc(`_clean: numToKeep = ${this.options.numToKeep}, existingFiles = ${e.length}`),bc("_clean: existing files are: ",e),this._tooManyFiles(e.length)){const n=e.slice(0,e.length-this.options.numToKeep).map((e=>Bc.format({dir:this.fileObject.dir,base:e.filename})));await(t=n,bc(`deleteFiles: files to delete: ${t}`),Promise.all(t.map((e=>_c.unlink(e).catch((t=>{bc(`deleteFiles: error when unlinking ${e}, ignoring. Error was ${t}`)}))))))}var t}_tooManyFiles(e){return this.options.numToKeep>0&&e>this.options.numToKeep}};const Lc=Mc;var jc=class extends Lc{constructor(e,t,n,r){r||(r={}),t&&(r.maxSize=t),r.numBackups||0===r.numBackups||(n||0===n||(n=1),r.numBackups=n),super(e,r),this.backups=r.numBackups,this.size=this.options.maxSize}get theStream(){return this.currentFileStream}};const $c=Mc;var Hc={RollingFileWriteStream:Mc,RollingFileStream:jc,DateRollingFileStream:class extends $c{constructor(e,t,n){t&&"object"==typeof t&&(n=t,t=null),n||(n={}),t||(t="yyyy-MM-dd"),n.pattern=t,n.numBackups||0===n.numBackups?n.daysToKeep=n.numBackups:(n.daysToKeep||0===n.daysToKeep?process.emitWarning("options.daysToKeep is deprecated due to the confusion it causes when used together with file size rolling. Please use options.numBackups instead.","DeprecationWarning","streamroller-DEP0001"):n.daysToKeep=1,n.numBackups=n.daysToKeep),super(e,n),this.mode=this.options.mode}get theStream(){return this.currentFileStream}}};const Jc=Nr.exports("log4js:file"),Gc=p.default,Vc=Hc,Uc=E.default.EOL;let Wc=!1;const zc=new Set;function Kc(){zc.forEach((e=>{e.sighupHandler()}))}function qc(e,t,n,r){const u=new Vc.RollingFileStream(e,t,n,r);return u.on("error",(t=>{console.error("log4js.fileAppender - Writing to file %s, error happened ",e,t)})),u.on("drain",(()=>{process.emit("log4js:pause",!1)})),u}Eo.configure=function(e,t){let n=t.basicLayout;return e.layout&&(n=t.layout(e.layout.type,e.layout)),e.mode=e.mode||384,function(e,t,n,r,u,o){e=Gc.normalize(e),Jc("Creating file appender (",e,", ",n,", ",r=r||0===r?r:5,", ",u,", ",o,")");let i=qc(e,n,r,u);const s=function(e){if(i.writable){if(!0===u.removeColor){const t=/\x1b[[0-9;]*m/g;e.data=e.data.map((e=>"string"==typeof e?e.replace(t,""):e))}i.write(t(e,o)+Uc,"utf8")||process.emit("log4js:pause",!0)}};return s.reopen=function(){i.end((()=>{i=qc(e,n,r,u)}))},s.sighupHandler=function(){Jc("SIGHUP handler called."),s.reopen()},s.shutdown=function(e){zc.delete(s),0===zc.size&&Wc&&(process.removeListener("SIGHUP",Kc),Wc=!1),i.end("","utf-8",e)},zc.add(s),Wc||(process.on("SIGHUP",Kc),Wc=!0),s}(e.filename,n,e.maxLogSize,e.backups,e,e.timezoneOffset)};var Yc={};const Xc=Hc,Zc=E.default.EOL;function Qc(e,t,n,r,u){r.maxSize=r.maxLogSize;const o=function(e,t,n){const r=new Xc.DateRollingFileStream(e,t,n);return r.on("error",(t=>{console.error("log4js.dateFileAppender - Writing to file %s, error happened ",e,t)})),r.on("drain",(()=>{process.emit("log4js:pause",!1)})),r}(e,t,r),i=function(e){o.writable&&(o.write(n(e,u)+Zc,"utf8")||process.emit("log4js:pause",!0))};return i.shutdown=function(e){o.end("","utf-8",e)},i}Yc.configure=function(e,t){let n=t.basicLayout;return e.layout&&(n=t.layout(e.layout.type,e.layout)),e.alwaysIncludePattern||(e.alwaysIncludePattern=!1),e.mode=e.mode||384,Qc(e.filename,e.pattern,n,e,e.timezoneOffset)};var ea={};const ta=Nr.exports("log4js:fileSync"),na=p.default,ra=D.default,ua=E.default.EOL||"\n";function oa(e,t){if(ra.existsSync(e))return;const n=ra.openSync(e,t.flags,t.mode);ra.closeSync(n)}class ia{constructor(e,t,n,r){ta("In RollingFileStream"),function(){if(!e||!t||t<=0)throw new Error("You must specify a filename and file size")}(),this.filename=e,this.size=t,this.backups=n,this.options=r,this.currentSize=0,this.currentSize=function(e){let t=0;try{t=ra.statSync(e).size}catch(t){oa(e,r)}return t}(this.filename)}shouldRoll(){return ta("should roll with current size %d, and max size %d",this.currentSize,this.size),this.currentSize>=this.size}roll(e){const t=this,n=new RegExp(`^${na.basename(e)}`);function r(e){return n.test(e)}function u(t){return parseInt(t.substring(`${na.basename(e)}.`.length),10)||0}function o(e,t){return u(e)>u(t)?1:u(e) ${e}.${r+1}`),ra.renameSync(na.join(na.dirname(e),n),`${e}.${r+1}`)}}ta("Rolling, rolling, rolling"),ta("Renaming the old files"),ra.readdirSync(na.dirname(e)).filter(r).sort(o).reverse().forEach(i)}write(e,t){const n=this;ta("in write"),this.shouldRoll()&&(this.currentSize=0,this.roll(this.filename)),ta("writing the chunk to the file"),n.currentSize+=e.length,ra.appendFileSync(n.filename,e)}}ea.configure=function(e,t){let n=t.basicLayout;e.layout&&(n=t.layout(e.layout.type,e.layout));const r={flags:e.flags||"a",encoding:e.encoding||"utf8",mode:e.mode||384};return function(e,t,n,r,u,o){ta("fileSync appender created");const i=function(e,t,n){let r;var u;return t?r=new ia(e,t,n,o):(oa(u=e,o),r={write(e){ra.appendFileSync(u,e)}}),r}(e=na.normalize(e),n,r=r||0===r?r:5);return e=>{i.write(t(e,u)+ua)}}(e.filename,n,e.maxLogSize,e.backups,e.timezoneOffset,r)};var sa={};const ca=Nr.exports("log4js:tcp"),aa=S.default;sa.configure=function(e,t){ca(`configure with config = ${e}`);let n=function(e){return e.serialise()};return e.layout&&(n=t.layout(e.layout.type,e.layout)),function(e,t){let n=!1;const r=[];let u,o=3,i="__LOG4JS__";function s(e){ca("Writing log event to socket"),n=u.write(`${t(e)}${i}`,"utf8")}function c(){let e;for(ca("emptying buffer");e=r.shift();)s(e)}function a(e){n?s(e):(ca("buffering log event because it cannot write at the moment"),r.push(e))}return function t(){ca(`appender creating socket to ${e.host||"localhost"}:${e.port||5e3}`),i=`${e.endMsg||"__LOG4JS__"}`,u=aa.createConnection(e.port||5e3,e.host||"localhost"),u.on("connect",(()=>{ca("socket connected"),c(),n=!0})),u.on("drain",(()=>{ca("drain event received, emptying buffer"),n=!0,c()})),u.on("timeout",u.end.bind(u)),u.on("error",(e=>{ca("connection error",e),n=!1,c()})),u.on("close",t)}(),a.shutdown=function(e){ca("shutdown called"),r.length&&o?(ca("buffer has items, waiting 100ms to empty"),o-=1,setTimeout((()=>{a.shutdown(e)}),100)):(u.removeAllListeners("close"),u.end(e))},a}(e,n)};const la=p.default,fa=Nr.exports("log4js:appenders"),da=tu,Da=eo,pa=gu,Ea=hu,ma=to,ha=new Map;ha.set("console",oo),ha.set("stdout",so),ha.set("stderr",co),ha.set("logLevelFilter",ao),ha.set("categoryFilter",lo),ha.set("noLogFilter",Do),ha.set("file",Eo),ha.set("dateFile",Yc),ha.set("fileSync",ea),ha.set("tcp",sa);const ya=new Map,Ca=(e,t)=>{fa("Loading module from ",e);try{return require(e)}catch(n){return void da.throwExceptionIf(t,"MODULE_NOT_FOUND"!==n.code,`appender "${e}" could not be loaded (error was: ${n})`)}},Fa=new Set,ga=(e,t)=>{if(ya.has(e))return ya.get(e);if(!t.appenders[e])return!1;if(Fa.has(e))throw new Error(`Dependency loop detected for appender ${e}.`);Fa.add(e),fa(`Creating appender ${e}`);const n=Aa(e,t);return Fa.delete(e),ya.set(e,n),n},Aa=(e,t)=>{const n=t.appenders[e],r=n.type.configure?n.type:((e,t)=>ha.get(e)||Ca(`./${e}`,t)||Ca(e,t)||require.main&&Ca(la.join(la.dirname(require.main.filename),e),t)||Ca(la.join(process.cwd(),e),t))(n.type,t);return da.throwExceptionIf(t,da.not(r),`appender "${e}" is not valid (type "${n.type}" could not be found)`),r.appender&&fa(`DEPRECATION: Appender ${n.type} exports an appender function.`),r.shutdown&&fa(`DEPRECATION: Appender ${n.type} exports a shutdown function.`),fa(`${e}: clustering.isMaster ? ${Da.isMaster()}`),fa(`${e}: appenderModule is ${F.default.inspect(r)}`),Da.onlyOnMaster((()=>(fa(`calling appenderModule.configure for ${e} / ${n.type}`),r.configure(ma.modifyConfig(n),Ea,(e=>ga(e,t)),pa))),(()=>{}))},va=e=>{ya.clear(),Fa.clear();const t=[];Object.values(e.categories).forEach((e=>{t.push(...e.appenders)})),Object.keys(e.appenders).forEach((n=>{(t.includes(n)||"tcp-server"===e.appenders[n].type)&&ga(n,e)}))},Sa=()=>{va({appenders:{out:{type:"stdout"}},categories:{default:{appenders:["out"],level:"trace"}}})};Sa(),da.addListener((e=>{da.throwExceptionIf(e,da.not(da.anObject(e.appenders)),'must have a property "appenders" of type object.');const t=Object.keys(e.appenders);da.throwExceptionIf(e,da.not(t.length),"must define at least one appender."),t.forEach((t=>{da.throwExceptionIf(e,da.not(e.appenders[t].type),`appender "${t}" is not valid (must be an object with property "type")`)}))})),da.addListener(va),Au.exports=ya,Au.exports.init=Sa;var wa={exports:{}};!function(e){const t=Nr.exports("log4js:categories"),n=tu,r=gu,u=Au.exports,o=new Map;function i(e,t,n){if(!1===t.inherit)return;const r=n.lastIndexOf(".");if(r<0)return;const u=n.substring(0,r);let o=e.categories[u];o||(o={inherit:!0,appenders:[]}),i(e,o,u),!e.categories[u]&&o.appenders&&o.appenders.length&&o.level&&(e.categories[u]=o),t.appenders=t.appenders||[],t.level=t.level||o.level,o.appenders.forEach((e=>{t.appenders.includes(e)||t.appenders.push(e)})),t.parent=o}function s(e){if(!e.categories)return;Object.keys(e.categories).forEach((t=>{const n=e.categories[t];i(e,n,t)}))}n.addPreProcessingListener((e=>s(e))),n.addListener((e=>{n.throwExceptionIf(e,n.not(n.anObject(e.categories)),'must have a property "categories" of type object.');const t=Object.keys(e.categories);n.throwExceptionIf(e,n.not(t.length),"must define at least one category."),t.forEach((t=>{const o=e.categories[t];n.throwExceptionIf(e,[n.not(o.appenders),n.not(o.level)],`category "${t}" is not valid (must be an object with properties "appenders" and "level")`),n.throwExceptionIf(e,n.not(Array.isArray(o.appenders)),`category "${t}" is not valid (appenders must be an array of appender names)`),n.throwExceptionIf(e,n.not(o.appenders.length),`category "${t}" is not valid (appenders must contain at least one appender name)`),Object.prototype.hasOwnProperty.call(o,"enableCallStack")&&n.throwExceptionIf(e,"boolean"!=typeof o.enableCallStack,`category "${t}" is not valid (enableCallStack must be boolean type)`),o.appenders.forEach((r=>{n.throwExceptionIf(e,n.not(u.get(r)),`category "${t}" is not valid (appender "${r}" is not defined)`)})),n.throwExceptionIf(e,n.not(r.getLevel(o.level)),`category "${t}" is not valid (level "${o.level}" not recognised; valid levels are ${r.levels.join(", ")})`)})),n.throwExceptionIf(e,n.not(e.categories.default),'must define a "default" category.')}));const c=e=>{o.clear();Object.keys(e.categories).forEach((n=>{const i=e.categories[n],s=[];i.appenders.forEach((e=>{s.push(u.get(e)),t(`Creating category ${n}`),o.set(n,{appenders:s,level:r.getLevel(i.level),enableCallStack:i.enableCallStack||!1})}))}))},a=()=>{c({categories:{default:{appenders:["out"],level:"OFF"}}})};a(),n.addListener(c);const l=e=>(t(`configForCategory: searching for config for ${e}`),o.has(e)?(t(`configForCategory: ${e} exists in config, returning it`),o.get(e)):e.indexOf(".")>0?(t(`configForCategory: ${e} has hierarchy, searching for parents`),l(e.substring(0,e.lastIndexOf(".")))):(t("configForCategory: returning config for default category"),l("default")));e.exports=o,e.exports=Object.assign(e.exports,{appendersForCategory:e=>l(e).appenders,getLevelForCategory:e=>l(e).level,setLevelForCategory:(e,n)=>{let r=o.get(e);if(t(`setLevelForCategory: found ${r} for ${e}`),!r){const n=l(e);t(`setLevelForCategory: no config found for category, found ${n} for parents of ${e}`),r={appenders:n.appenders}}r.level=n,o.set(e,r)},getEnableCallStackForCategory:e=>!0===l(e).enableCallStack,setEnableCallStackForCategory:(e,t)=>{l(e).enableCallStack=t},init:a})}(wa);const Oa=Nr.exports("log4js:logger"),ba=Hu,_a=gu,Ba=eo,Pa=wa.exports,ka=tu,xa=/at (?:(.+)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/;function Na(e,t=4){const n=e.stack.split("\n").slice(t),r=xa.exec(n[0]);return r&&6===r.length?{functionName:r[1],fileName:r[2],lineNumber:parseInt(r[3],10),columnNumber:parseInt(r[4],10),callStack:n.join("\n")}:null}class Ia{constructor(e){if(!e)throw new Error("No category provided.");this.category=e,this.context={},this.parseCallStack=Na,Oa(`Logger created (${this.category}, ${this.level})`)}get level(){return _a.getLevel(Pa.getLevelForCategory(this.category),_a.TRACE)}set level(e){Pa.setLevelForCategory(this.category,_a.getLevel(e,this.level))}get useCallStack(){return Pa.getEnableCallStackForCategory(this.category)}set useCallStack(e){Pa.setEnableCallStackForCategory(this.category,!0===e)}log(e,...t){let n=_a.getLevel(e);n||(this._log(_a.WARN,"log4js:logger.log: invalid value for log-level as first parameter given: ",e),n=_a.INFO),this.isLevelEnabled(n)&&this._log(n,t)}isLevelEnabled(e){return this.level.isLessThanOrEqualTo(e)}_log(e,t){Oa(`sending log data (${e}) to appenders`);const n=new ba(this.category,e,t,this.context,this.useCallStack&&this.parseCallStack(new Error));Ba.send(n)}addContext(e,t){this.context[e]=t}removeContext(e){delete this.context[e]}clearContext(){this.context={}}setParseCallStackFunction(e){this.parseCallStack=e}}function Ta(e){const t=_a.getLevel(e),n=t.toString().toLowerCase().replace(/_([a-z])/g,(e=>e[1].toUpperCase())),r=n[0].toUpperCase()+n.slice(1);Ia.prototype[`is${r}Enabled`]=function(){return this.isLevelEnabled(t)},Ia.prototype[n]=function(...e){this.log(t,...e)}}_a.levels.forEach(Ta),ka.addListener((()=>{_a.levels.forEach(Ta)}));var Ra=Ia;const Ma=gu;function La(e){return e.originalUrl||e.url}function ja(e,t){for(let n=0;ne.source?e.source:e));t=new RegExp(n.join("|"))}return t}(t.nolog);return(e,i,s)=>{if(e._logging)return s();if(o&&o.test(e.originalUrl))return s();if(n.isLevelEnabled(r)||"auto"===t.level){const o=new Date,{writeHead:s}=i;e._logging=!0,i.writeHead=(e,t)=>{i.writeHead=s,i.writeHead(e,t),i.__statusCode=e,i.__headers=t||{}},i.on("finish",(()=>{i.responseTime=new Date-o,i.statusCode&&"auto"===t.level&&(r=Ma.INFO,i.statusCode>=300&&(r=Ma.WARN),i.statusCode>=400&&(r=Ma.ERROR)),r=function(e,t,n){let r=t;if(n){const t=n.find((t=>{let n=!1;return n=t.from&&t.to?e>=t.from&&e<=t.to:-1!==t.codes.indexOf(e),n}));t&&(r=Ma.getLevel(t.level,r))}return r}(i.statusCode,r,t.statusRules);const s=function(e,t,n){const r=[];return r.push({token:":url",replacement:La(e)}),r.push({token:":protocol",replacement:e.protocol}),r.push({token:":hostname",replacement:e.hostname}),r.push({token:":method",replacement:e.method}),r.push({token:":status",replacement:t.__statusCode||t.statusCode}),r.push({token:":response-time",replacement:t.responseTime}),r.push({token:":date",replacement:(new Date).toUTCString()}),r.push({token:":referrer",replacement:e.headers.referer||e.headers.referrer||""}),r.push({token:":http-version",replacement:`${e.httpVersionMajor}.${e.httpVersionMinor}`}),r.push({token:":remote-addr",replacement:e.headers["x-forwarded-for"]||e.ip||e._remoteAddress||e.socket&&(e.socket.remoteAddress||e.socket.socket&&e.socket.socket.remoteAddress)}),r.push({token:":user-agent",replacement:e.headers["user-agent"]}),r.push({token:":content-length",replacement:t.getHeader("content-length")||t.__headers&&t.__headers["Content-Length"]||"-"}),r.push({token:/:req\[([^\]]+)]/g,replacement:(t,n)=>e.headers[n.toLowerCase()]}),r.push({token:/:res\[([^\]]+)]/g,replacement:(e,n)=>t.getHeader(n.toLowerCase())||t.__headers&&t.__headers[n]}),(e=>{const t=e.concat();for(let e=0;eja(e,s)));t&&n.log(r,t)}else n.log(r,ja(u,s));t.context&&n.removeContext("res")}))}return s()}},nl=Va;let rl=!1;function ul(e){if(!rl)return;Ua("Received log event ",e);Za.appendersForCategory(e.categoryName).forEach((t=>{t(e)}))}function ol(e){rl&&il();let t=e;return"string"==typeof t&&(t=function(e){Ua(`Loading configuration from ${e}`);try{return JSON.parse(Wa.readFileSync(e,"utf8"))}catch(t){throw new Error(`Problem reading config from file "${e}". Error was ${t.message}`,t)}}(e)),Ua(`Configuration is ${t}`),Ka.configure(za(t)),el.onMessage(ul),rl=!0,sl}function il(e){Ua("Shutdown called. Disabling all log writing."),rl=!1;const t=Array.from(Xa.values());Xa.init(),Za.init();const n=t.reduceRight(((e,t)=>t.shutdown?e+1:e),0);if(0===n)return Ua("No appenders with shutdown functions found."),void 0!==e&&e();let r,u=0;function o(t){r=r||t,u+=1,Ua(`Appender shutdowns complete: ${u} / ${n}`),u>=n&&(Ua("All shutdown functions completed."),e&&e(r))}return Ua(`Found ${n} appenders with shutdown functions.`),t.filter((e=>e.shutdown)).forEach((e=>e.shutdown(o))),null}const sl={getLogger:function(e){return rl||ol(process.env.LOG4JS_CONFIG||{appenders:{out:{type:"stdout"}},categories:{default:{appenders:["out"],level:"OFF"}}}),new Qa(e||"default")},configure:ol,shutdown:il,connectLogger:tl,levels:Ya,addLayout:qa.addLayout,recording:function(){return nl}};var cl=sl,al={};Object.defineProperty(al,"__esModule",{value:!0}),al.levelMap=al.getLevel=al.setCategoriesLevel=al.getConfiguration=al.setConfiguration=void 0;const ll=cl;let fl={appenders:{debug:{type:"stdout",layout:{type:"pattern",pattern:"[%d] > hvigor %p %c %[%m%]"}},info:{type:"stdout",layout:{type:"pattern",pattern:"[%d] > hvigor %[%m%]"}},"no-pattern-info":{type:"stdout",layout:{type:"pattern",pattern:"%m"}},wrong:{type:"stderr",layout:{type:"pattern",pattern:"[%d] > hvigor %[%p: %m%]"}},"just-debug":{type:"logLevelFilter",appender:"debug",level:"debug",maxLevel:"debug"},"just-info":{type:"logLevelFilter",appender:"info",level:"info",maxLevel:"info"},"just-wrong":{type:"logLevelFilter",appender:"wrong",level:"warn",maxLevel:"error"}},categories:{default:{appenders:["just-debug","just-info","just-wrong"],level:"debug"},"no-pattern-info":{appenders:["no-pattern-info"],level:"info"}}};al.setConfiguration=e=>{fl=e};al.getConfiguration=()=>fl;let dl=ll.levels.DEBUG;al.setCategoriesLevel=(e,t)=>{dl=e;const n=fl.categories;for(const r in n)(null==t?void 0:t.includes(r))||Object.prototype.hasOwnProperty.call(n,r)&&(n[r].level=e.levelStr)};al.getLevel=()=>dl,al.levelMap=new Map([["ALL",ll.levels.ALL],["MARK",ll.levels.MARK],["TRACE",ll.levels.TRACE],["DEBUG",ll.levels.DEBUG],["INFO",ll.levels.INFO],["WARN",ll.levels.WARN],["ERROR",ll.levels.ERROR],["FATAL",ll.levels.FATAL],["OFF",ll.levels.OFF]]);var Dl=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),pl=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),El=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&Dl(t,e,n);return pl(t,e),t};Object.defineProperty(xr,"__esModule",{value:!0}),xr.evaluateLogLevel=xr.HvigorLogger=void 0;const ml=El(cl),hl=cl,yl=El(F.default),Cl=al;class Fl{constructor(e){ml.configure((0,Cl.getConfiguration)()),this._logger=ml.getLogger(e),this._logger.level=(0,Cl.getLevel)()}static getLogger(e){return new Fl(e)}log(e,...t){this._logger.log(e,...t)}debug(e,...t){this._logger.debug(e,...t)}info(e,...t){this._logger.info(e,...t)}warn(e,...t){void 0!==e&&""!==e&&this._logger.warn(e,...t)}error(e,...t){this._logger.error(e,...t)}_printTaskExecuteInfo(e,t){this.info(`Finished :${e}... after ${t}`)}_printFailedTaskInfo(e){this.error(`Failed :${e}... `)}_printDisabledTaskInfo(e){this.info(`Disabled :${e}... `)}_printUpToDateTaskInfo(e){this.info(`UP-TO-DATE :${e}... `)}errorMessageExit(e,...t){throw new Error(yl.format(e,...t))}errorExit(e,t,...n){t&&this._logger.error(t,n),this._logger.error(e.stack)}setLevel(e,t){(0,Cl.setCategoriesLevel)(e,t),ml.shutdown(),ml.configure((0,Cl.getConfiguration)())}getLevel(){return this._logger.level}configure(e){const t=(0,Cl.getConfiguration)(),n={appenders:{...t.appenders,...e.appenders},categories:{...t.categories,...e.categories}};(0,Cl.setConfiguration)(n),ml.shutdown(),ml.configure(n)}}xr.HvigorLogger=Fl,xr.evaluateLogLevel=function(e,t){t.debug?e.setLevel(hl.levels.DEBUG):t.warn?e.setLevel(hl.levels.WARN):t.error?e.setLevel(hl.levels.ERROR):e.setLevel(hl.levels.INFO)};var gl=w&&w.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(X,"__esModule",{value:!0}),X.parseJsonText=X.parseJsonFile=void 0;const Al=Z,vl=gl(kr),Sl=gl(p.default),wl=gl(E.default),Ol=xr.HvigorLogger.getLogger("parse-json-util");var bl;!function(e){e[e.Char=0]="Char",e[e.EOF=1]="EOF",e[e.Identifier=2]="Identifier"}(bl||(bl={}));let _l,Bl,Pl,kl,xl,Nl,Il="start",Tl=[],Rl=0,Ml=1,Ll=0,jl=!1,$l="default",Hl="'",Jl=1;function Gl(e,t=!1){Bl=String(e),Il="start",Tl=[],Rl=0,Ml=1,Ll=0,kl=void 0,jl=t;do{_l=Vl(),Xl[Il]()}while("eof"!==_l.type);return kl}function Vl(){for($l="default",xl="",Hl="'",Jl=1;;){Nl=Ul();const e=zl[$l]();if(e)return e}}function Ul(){if(Bl[Rl])return String.fromCodePoint(Bl.codePointAt(Rl))}function Wl(){const e=Ul();return"\n"===e?(Ml++,Ll=0):e?Ll+=e.length:Ll++,e&&(Rl+=e.length),e}X.parseJsonFile=function(e,t=!1,n="utf-8"){const r=vl.default.readFileSync(Sl.default.resolve(e),{encoding:n});try{return Gl(r,t)}catch(t){if(t instanceof SyntaxError){const n=t.message.split("at");2===n.length&&Ol.errorMessageExit(`${n[0].trim()}${wl.default.EOL}\t at ${e}:${n[1].trim()}`)}Ol.errorMessageExit(`${e} is not in valid JSON/JSON5 format.`)}},X.parseJsonText=Gl;const zl={default(){switch(Nl){case"/":return Wl(),void($l="comment");case void 0:return Wl(),Kl("eof")}if(!Al.JudgeUtil.isIgnoreChar(Nl)&&!Al.JudgeUtil.isSpaceSeparator(Nl))return zl[Il]();Wl()},start(){$l="value"},beforePropertyName(){switch(Nl){case"$":case"_":return xl=Wl(),void($l="identifierName");case"\\":return Wl(),void($l="identifierNameStartEscape");case"}":return Kl("punctuator",Wl());case'"':case"'":return Hl=Nl,Wl(),void($l="string")}if(Al.JudgeUtil.isIdStartChar(Nl))return xl+=Wl(),void($l="identifierName");throw tf(bl.Char,Wl())},afterPropertyName(){if(":"===Nl)return Kl("punctuator",Wl());throw tf(bl.Char,Wl())},beforePropertyValue(){$l="value"},afterPropertyValue(){switch(Nl){case",":case"}":return Kl("punctuator",Wl())}throw tf(bl.Char,Wl())},beforeArrayValue(){if("]"===Nl)return Kl("punctuator",Wl());$l="value"},afterArrayValue(){switch(Nl){case",":case"]":return Kl("punctuator",Wl())}throw tf(bl.Char,Wl())},end(){throw tf(bl.Char,Wl())},comment(){switch(Nl){case"*":return Wl(),void($l="multiLineComment");case"/":return Wl(),void($l="singleLineComment")}throw tf(bl.Char,Wl())},multiLineComment(){switch(Nl){case"*":return Wl(),void($l="multiLineCommentAsterisk");case void 0:throw tf(bl.Char,Wl())}Wl()},multiLineCommentAsterisk(){switch(Nl){case"*":return void Wl();case"/":return Wl(),void($l="default");case void 0:throw tf(bl.Char,Wl())}Wl(),$l="multiLineComment"},singleLineComment(){switch(Nl){case"\n":case"\r":case"\u2028":case"\u2029":return Wl(),void($l="default");case void 0:return Wl(),Kl("eof")}Wl()},value(){switch(Nl){case"{":case"[":return Kl("punctuator",Wl());case"n":return Wl(),ql("ull"),Kl("null",null);case"t":return Wl(),ql("rue"),Kl("boolean",!0);case"f":return Wl(),ql("alse"),Kl("boolean",!1);case"-":case"+":return"-"===Wl()&&(Jl=-1),void($l="numerical");case".":case"0":case"I":case"N":return void($l="numerical");case'"':case"'":return Hl=Nl,Wl(),xl="",void($l="string")}if(void 0===Nl||!Al.JudgeUtil.isDigitWithoutZero(Nl))throw tf(bl.Char,Wl());$l="numerical"},numerical(){switch(Nl){case".":return xl=Wl(),void($l="decimalPointLeading");case"0":return xl=Wl(),void($l="zero");case"I":return Wl(),ql("nfinity"),Kl("numeric",Jl*(1/0));case"N":return Wl(),ql("aN"),Kl("numeric",NaN)}if(void 0!==Nl&&Al.JudgeUtil.isDigitWithoutZero(Nl))return xl=Wl(),void($l="decimalInteger");throw tf(bl.Char,Wl())},zero(){switch(Nl){case".":case"e":case"E":return void($l="decimal");case"x":case"X":return xl+=Wl(),void($l="hexadecimal")}return Kl("numeric",0)},decimalInteger(){switch(Nl){case".":case"e":case"E":return void($l="decimal")}if(!Al.JudgeUtil.isDigit(Nl))return Kl("numeric",Jl*Number(xl));xl+=Wl()},decimal(){switch(Nl){case".":xl+=Wl(),$l="decimalFraction";break;case"e":case"E":xl+=Wl(),$l="decimalExponent"}},decimalPointLeading(){if(Al.JudgeUtil.isDigit(Nl))return xl+=Wl(),void($l="decimalFraction");throw tf(bl.Char,Wl())},decimalFraction(){switch(Nl){case"e":case"E":return xl+=Wl(),void($l="decimalExponent")}if(!Al.JudgeUtil.isDigit(Nl))return Kl("numeric",Jl*Number(xl));xl+=Wl()},decimalExponent(){switch(Nl){case"+":case"-":return xl+=Wl(),void($l="decimalExponentSign")}if(Al.JudgeUtil.isDigit(Nl))return xl+=Wl(),void($l="decimalExponentInteger");throw tf(bl.Char,Wl())},decimalExponentSign(){if(Al.JudgeUtil.isDigit(Nl))return xl+=Wl(),void($l="decimalExponentInteger");throw tf(bl.Char,Wl())},decimalExponentInteger(){if(!Al.JudgeUtil.isDigit(Nl))return Kl("numeric",Jl*Number(xl));xl+=Wl()},hexadecimal(){if(Al.JudgeUtil.isHexDigit(Nl))return xl+=Wl(),void($l="hexadecimalInteger");throw tf(bl.Char,Wl())},hexadecimalInteger(){if(!Al.JudgeUtil.isHexDigit(Nl))return Kl("numeric",Jl*Number(xl));xl+=Wl()},identifierNameStartEscape(){if("u"!==Nl)throw tf(bl.Char,Wl());Wl();const e=Yl();switch(e){case"$":case"_":break;default:if(!Al.JudgeUtil.isIdStartChar(e))throw tf(bl.Identifier)}xl+=e,$l="identifierName"},identifierName(){switch(Nl){case"$":case"_":case"‌":case"‍":return void(xl+=Wl());case"\\":return Wl(),void($l="identifierNameEscape")}if(!Al.JudgeUtil.isIdContinueChar(Nl))return Kl("identifier",xl);xl+=Wl()},identifierNameEscape(){if("u"!==Nl)throw tf(bl.Char,Wl());Wl();const e=Yl();switch(e){case"$":case"_":case"‌":case"‍":break;default:if(!Al.JudgeUtil.isIdContinueChar(e))throw tf(bl.Identifier)}xl+=e,$l="identifierName"},string(){switch(Nl){case"\\":return Wl(),void(xl+=function(){const e=Ul(),t=function(){switch(Ul()){case"b":return Wl(),"\b";case"f":return Wl(),"\f";case"n":return Wl(),"\n";case"r":return Wl(),"\r";case"t":return Wl(),"\t";case"v":return Wl(),"\v"}return}();if(t)return t;switch(e){case"0":if(Wl(),Al.JudgeUtil.isDigit(Ul()))throw tf(bl.Char,Wl());return"\0";case"x":return Wl(),function(){let e="",t=Ul();if(!Al.JudgeUtil.isHexDigit(t))throw tf(bl.Char,Wl());if(e+=Wl(),t=Ul(),!Al.JudgeUtil.isHexDigit(t))throw tf(bl.Char,Wl());return e+=Wl(),String.fromCodePoint(parseInt(e,16))}();case"u":return Wl(),Yl();case"\n":case"\u2028":case"\u2029":return Wl(),"";case"\r":return Wl(),"\n"===Ul()&&Wl(),""}if(void 0===e||Al.JudgeUtil.isDigitWithoutZero(e))throw tf(bl.Char,Wl());return Wl()}());case'"':case"'":if(Nl===Hl){const e=Kl("string",xl);return Wl(),e}return void(xl+=Wl());case"\n":case"\r":case void 0:throw tf(bl.Char,Wl());case"\u2028":case"\u2029":!function(e){Ol.warn(`JSON5: '${ef(e)}' in strings is not valid ECMAScript; consider escaping.`)}(Nl)}xl+=Wl()}};function Kl(e,t){return{type:e,value:t,line:Ml,column:Ll}}function ql(e){for(const t of e){if(Ul()!==t)throw tf(bl.Char,Wl());Wl()}}function Yl(){let e="",t=4;for(;t-- >0;){const t=Ul();if(!Al.JudgeUtil.isHexDigit(t))throw tf(bl.Char,Wl());e+=Wl()}return String.fromCodePoint(parseInt(e,16))}const Xl={start(){if("eof"===_l.type)throw tf(bl.EOF);Zl()},beforePropertyName(){switch(_l.type){case"identifier":case"string":return Pl=_l.value,void(Il="afterPropertyName");case"punctuator":return void Ql();case"eof":throw tf(bl.EOF)}},afterPropertyName(){if("eof"===_l.type)throw tf(bl.EOF);Il="beforePropertyValue"},beforePropertyValue(){if("eof"===_l.type)throw tf(bl.EOF);Zl()},afterPropertyValue(){if("eof"===_l.type)throw tf(bl.EOF);switch(_l.value){case",":return void(Il="beforePropertyName");case"}":Ql()}},beforeArrayValue(){if("eof"===_l.type)throw tf(bl.EOF);"punctuator"!==_l.type||"]"!==_l.value?Zl():Ql()},afterArrayValue(){if("eof"===_l.type)throw tf(bl.EOF);switch(_l.value){case",":return void(Il="beforeArrayValue");case"]":Ql()}},end(){}};function Zl(){const e=function(){let e;switch(_l.type){case"punctuator":switch(_l.value){case"{":e={};break;case"[":e=[]}break;case"null":case"boolean":case"numeric":case"string":e=_l.value}return e}();if(jl&&"object"==typeof e&&(e._line=Ml,e._column=Ll),void 0===kl)kl=e;else{const t=Tl[Tl.length-1];Array.isArray(t)?jl&&"object"!=typeof e?t.push({value:e,_line:Ml,_column:Ll}):t.push(e):t[Pl]=jl&&"object"!=typeof e?{value:e,_line:Ml,_column:Ll}:e}!function(e){if(e&&"object"==typeof e)Tl.push(e),Il=Array.isArray(e)?"beforeArrayValue":"beforePropertyName";else{const e=Tl[Tl.length-1];Il=e?Array.isArray(e)?"afterArrayValue":"afterPropertyValue":"end"}}(e)}function Ql(){Tl.pop();const e=Tl[Tl.length-1];Il=e?Array.isArray(e)?"afterArrayValue":"afterPropertyValue":"end"}function ef(e){const t={"'":"\\'",'"':'\\"',"\\":"\\\\","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\v":"\\v","\0":"\\0","\u2028":"\\u2028","\u2029":"\\u2029"};if(t[e])return t[e];if(e<" "){const t=e.charCodeAt(0).toString(16);return`\\x${`00${t}`.substring(t.length)}`}return e}function tf(e,t){let n="";switch(e){case bl.Char:n=void 0===t?`JSON5: invalid end of input at ${Ml}:${Ll}`:`JSON5: invalid character '${ef(t)}' at ${Ml}:${Ll}`;break;case bl.EOF:n=`JSON5: invalid end of input at ${Ml}:${Ll}`;break;case bl.Identifier:Ll-=5,n=`JSON5: invalid identifier character at ${Ml}:${Ll}`}const r=new nf(n);return r.lineNumber=Ml,r.columnNumber=Ll,r}class nf extends SyntaxError{}var rf=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),uf=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),of=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&rf(t,e,n);return uf(t,e),t};Object.defineProperty(Y,"__esModule",{value:!0});var sf=Y.cleanWorkSpace=Ff=Y.executeInstallHvigor=yf=Y.isHvigorInstalled=mf=Y.isAllDependenciesInstalled=void 0;const cf=of(D.default),af=of(p.default),lf=b,ff=j,df=$,Df=X;let pf,Ef;var mf=Y.isAllDependenciesInstalled=function(){function e(e){const t=null==e?void 0:e.dependencies;return void 0===t?0:Object.getOwnPropertyNames(t).length}if(pf=gf(),Ef=Af(),e(pf)+1!==e(Ef))return!1;for(const e in null==pf?void 0:pf.dependencies)if(!(0,ff.hasNpmPackInPaths)(e,[lf.HVIGOR_PROJECT_DEPENDENCIES_HOME])||!hf(e,pf,Ef))return!1;return!0};function hf(e,t,n){return void 0!==n.dependencies&&(0,ff.offlinePluginConversion)(lf.HVIGOR_PROJECT_ROOT_DIR,t.dependencies[e])===n.dependencies[e]}var yf=Y.isHvigorInstalled=function(){return pf=gf(),Ef=Af(),(0,ff.hasNpmPackInPaths)(lf.HVIGOR_ENGINE_PACKAGE_NAME,[lf.HVIGOR_PROJECT_DEPENDENCIES_HOME])&&(0,ff.offlinePluginConversion)(lf.HVIGOR_PROJECT_ROOT_DIR,pf.hvigorVersion)===Ef.dependencies[lf.HVIGOR_ENGINE_PACKAGE_NAME]};const Cf={cwd:lf.HVIGOR_PROJECT_DEPENDENCIES_HOME,stdio:["inherit","inherit","inherit"]};var Ff=Y.executeInstallHvigor=function(){(0,df.logInfoPrintConsole)("Hvigor installing...");const e={dependencies:{}};e.dependencies[lf.HVIGOR_ENGINE_PACKAGE_NAME]=(0,ff.offlinePluginConversion)(lf.HVIGOR_PROJECT_ROOT_DIR,pf.hvigorVersion);try{cf.mkdirSync(lf.HVIGOR_PROJECT_DEPENDENCIES_HOME,{recursive:!0});const t=af.resolve(lf.HVIGOR_PROJECT_DEPENDENCIES_HOME,lf.DEFAULT_PACKAGE_JSON);cf.writeFileSync(t,JSON.stringify(e))}catch(e){(0,df.logErrorAndExit)(e)}!function(){const e=["config","set","store-dir",lf.HVIGOR_PNPM_STORE_PATH];(0,ff.executeCommand)(lf.HVIGOR_WRAPPER_PNPM_SCRIPT_PATH,e,Cf)}(),(0,ff.executeCommand)(lf.HVIGOR_WRAPPER_PNPM_SCRIPT_PATH,["install"],Cf)};function gf(){const e=af.resolve(lf.HVIGOR_PROJECT_WRAPPER_HOME,lf.DEFAULT_HVIGOR_CONFIG_JSON_FILE_NAME);return cf.existsSync(e)||(0,df.logErrorAndExit)(`Error: Hvigor config file ${e} does not exist.`),(0,Df.parseJsonFile)(e)}function Af(){return cf.existsSync(lf.HVIGOR_PROJECT_DEPENDENCY_PACKAGE_JSON_PATH)?(0,Df.parseJsonFile)(lf.HVIGOR_PROJECT_DEPENDENCY_PACKAGE_JSON_PATH):{dependencies:{}}}sf=Y.cleanWorkSpace=function(){if((0,df.logInfoPrintConsole)("Hvigor cleaning..."),!cf.existsSync(lf.HVIGOR_PROJECT_DEPENDENCIES_HOME))return;const e=cf.readdirSync(lf.HVIGOR_PROJECT_DEPENDENCIES_HOME);if(e&&0!==e.length){cf.existsSync(lf.HVIGOR_BOOT_JS_FILE_PATH)&&(0,ff.executeCommand)(process.argv[0],[lf.HVIGOR_BOOT_JS_FILE_PATH,"--stop-daemon"],{});try{e.forEach((e=>{cf.rmSync(af.resolve(lf.HVIGOR_PROJECT_DEPENDENCIES_HOME,e),{recursive:!0})}))}catch(e){(0,df.logErrorAndExit)(`The hvigor build tool cannot be installed. Please manually clear the workspace directory and synchronize the project again.\n\n Workspace Path: ${lf.HVIGOR_PROJECT_DEPENDENCIES_HOME}.`)}}};var vf={},Sf=w&&w.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var u=Object.getOwnPropertyDescriptor(t,n);u&&!("get"in u?!t.__esModule:u.writable||u.configurable)||(u={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,u)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),wf=w&&w.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),Of=w&&w.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&Sf(t,e,n);return wf(t,e),t};Object.defineProperty(vf,"__esModule",{value:!0});var bf=vf.executeBuild=void 0;const _f=b,Bf=Of(D.default),Pf=Of(p.default),kf=$;bf=vf.executeBuild=function(){const e=Pf.resolve(_f.HVIGOR_PROJECT_DEPENDENCIES_HOME,"node_modules","@ohos","hvigor","bin","hvigor.js");try{const t=Bf.realpathSync(e);require(t)}catch(t){(0,kf.logErrorAndExit)(`Error: ENOENT: no such file ${e},delete ${_f.HVIGOR_PROJECT_DEPENDENCIES_HOME} and retry.`)}},function(){if(O.checkNpmConifg(),O.environmentHandler(),O.isPnpmAvailable()||O.executeInstallPnpm(),yf()&&mf())bf();else{sf();try{Ff()}catch(e){return void sf()}bf()}}(); \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/hvigorfile.ts b/shell/platform/ohos/flutter_embedding/hvigorfile.ts new file mode 100755 index 0000000000..5a172b770e --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/hvigorfile.ts @@ -0,0 +1,17 @@ +/* +* 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. +*/ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { appTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/hvigorw b/shell/platform/ohos/flutter_embedding/hvigorw new file mode 100755 index 0000000000..54aadd226b --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/hvigorw @@ -0,0 +1,48 @@ +#!/bin/bash + +# ---------------------------------------------------------------------------- +# Hvigor startup script, version 1.0.0 +# +# Required ENV vars: +# ------------------ +# NODE_HOME - location of a Node home dir +# or +# Add /usr/local/nodejs/bin to the PATH environment variable +# ---------------------------------------------------------------------------- + +HVIGOR_APP_HOME=$(dirname $(readlink -f $0)) +HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js +warn() { + echo "" + echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m" +} + +error() { + echo "" + echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m" +} + +fail() { + error "$@" + exit 1 +} + +# Determine node to start hvigor wrapper script +if [ -n "${NODE_HOME}" ];then + EXECUTABLE_NODE="${NODE_HOME}/bin/node" + if [ ! -x "$EXECUTABLE_NODE" ];then + fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed" + fi +else + EXECUTABLE_NODE="node" + which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path" +fi + +# Check hvigor wrapper script +if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then + fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}" +fi + +# start hvigor-wrapper script +exec "${EXECUTABLE_NODE}" \ + "${HVIGOR_WRAPPER_SCRIPT}" "$@" diff --git a/shell/platform/ohos/flutter_embedding/hvigorw.bat b/shell/platform/ohos/flutter_embedding/hvigorw.bat new file mode 100755 index 0000000000..6861293e47 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/hvigorw.bat @@ -0,0 +1,64 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Hvigor startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js +set NODE_EXE=node.exe + +goto start + +:start +@rem Find node.exe +if defined NODE_HOME goto findNodeFromNodeHome + +%NODE_EXE% --version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH. +echo. +echo Please set the NODE_HOME variable in your environment to match the +echo location of your NodeJs installation. + +goto fail + +:findNodeFromNodeHome +set NODE_HOME=%NODE_HOME:"=% +set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE% + +if exist "%NODE_EXE_PATH%" goto execute +echo. +echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH. +echo. +echo Please set the NODE_HOME variable in your environment to match the +echo location of your NodeJs installation. + +goto fail + +:execute +@rem Execute hvigor +"%NODE_EXE%" %WRAPPER_MODULE_PATH% %* + +if "%ERRORLEVEL%" == "0" goto hvigorwEnd + +:fail +exit /b 1 + +:hvigorwEnd +if "%OS%" == "Windows_NT" endlocal + +:end diff --git a/shell/platform/ohos/flutter_embedding/local.properties b/shell/platform/ohos/flutter_embedding/local.properties new file mode 100755 index 0000000000..42b3cf913e --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/local.properties @@ -0,0 +1,23 @@ +# 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. + +# This file is automatically generated by DevEco Studio. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file should *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# For customization when using a Version Control System, please read the header note. +sdk.dir= +nodejs.dir= + diff --git a/shell/platform/ohos/flutter_embedding/oh-package-lock.json5 b/shell/platform/ohos/flutter_embedding/oh-package-lock.json5 new file mode 100755 index 0000000000..1171a787cc --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/oh-package-lock.json5 @@ -0,0 +1,28 @@ +/* +* 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. +*/ +{ + "lockfileVersion": 1, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hypium@1.0.6": "@ohos/hypium@1.0.6", + "@ohos/hypium@^1.0.6": "@ohos/hypium@1.0.6" + }, + "packages": { + "@ohos/hypium@1.0.6": { + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.6.tgz", + "integrity": "sha512-bb3DWeWhYrFqj9mPFV3yZQpkm36kbcK+YYaeY9g292QKSjOdmhEIQR2ULPvyMsgSR4usOBf5nnYrDmaCCXirgQ==" + } + } +} \ No newline at end of file diff --git a/shell/platform/ohos/flutter_embedding/oh-package.json5 b/shell/platform/ohos/flutter_embedding/oh-package.json5 new file mode 100755 index 0000000000..fc9afd9a53 --- /dev/null +++ b/shell/platform/ohos/flutter_embedding/oh-package.json5 @@ -0,0 +1,28 @@ +/* +* 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. +*/ + +{ + "name": "config", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.6" + } +} -- Gitee