diff --git a/packages/url_launcher/url_launcher_ohos/.gitignore b/packages/url_launcher/url_launcher_ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..894df49349702b4499f23d1dbc7beec90c271b2f --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages + +pubspec.lock +GeneratedPluginRegistrant* +ohos/**/oh_modules +ohos/**.har +ohos/**/BuildProfile.ets +ohos/**/oh-package-lock.json5 \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/AUTHORS b/packages/url_launcher/url_launcher_ohos/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..7ef91ed3886ed875c775d2df82b47f09bb09104a --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/AUTHORS @@ -0,0 +1,18 @@ +# 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. + +# Below is a list of people and organizations that have contributed +# to the Flutter project. Names should be added to the list like so: +# +# Name/Organization + diff --git a/packages/url_launcher/url_launcher_ohos/CHANGELOG.md b/packages/url_launcher/url_launcher_ohos/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/url_launcher/url_launcher_ohos/LICENSE b/packages/url_launcher/url_launcher_ohos/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..253434730ebe9ce041c259f1e61bcb346704cdb9 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/LICENSE @@ -0,0 +1,38 @@ +# 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. + +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/url_launcher/url_launcher_ohos/README.md b/packages/url_launcher/url_launcher_ohos/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9d9d35bdf06bdfee49e2d712a5c268f9547d7042 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/README.md @@ -0,0 +1,19 @@ +# url\_launcher\_ohos + +The Ohos implementation of [`url_launcher`][1]. + +## Usage + +This package is [endorsed][2], which means you can simply use `url_launcher` +normally. This package will be automatically included in your app when you do, +so you do not need to add it to your `pubspec.yaml`. + +However, if you `import` this package to use any of its APIs directly, you +should add it to your `pubspec.yaml` as usual. + +[1]: https://pub.dev/packages/url_launcher +[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin + + +## FAQ +The 'Launch in app' feature is not supported. Please use the modified feature through native application configuration. \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/README.md b/packages/url_launcher/url_launcher_ohos/example/README.md new file mode 100644 index 0000000000000000000000000000000000000000..96b8bb17dbff8e7acd9a1dd59c65ff40b96e1228 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/README.md @@ -0,0 +1,9 @@ +# Platform Implementation Test App + +This is a test app for manual testing and automated integration testing +of this platform implementation. It is not intended to demonstrate actual use of +this package, since the intent is that plugin clients use the app-facing +package. + +Unless you are making changes to this implementation package, this example is +very unlikely to be relevant. diff --git a/packages/url_launcher/url_launcher_ohos/example/lib/main.dart b/packages/url_launcher/url_launcher_ohos/example/lib/main.dart new file mode 100644 index 0000000000000000000000000000000000000000..36057db24b7d5d0663e2762f446d0640b75ee63a --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/lib/main.dart @@ -0,0 +1,265 @@ +/* + * 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. + */ + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'URL Launcher', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MyHomePage(title: 'URL Launcher'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; + bool _hasCallSupport = false; + Future? _launched; + String _phone = ''; + + @override + void initState() { + super.initState(); + // Check for phone call support. + launcher.canLaunch('tel:123').then((bool result) { + setState(() { + _hasCallSupport = result; + }); + }); + } + + Future _launchInBrowser(String url) async { + if (!await launcher.launch( + url, + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: {}, + )) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInWebView(String url) async { + if (!await launcher.launch( + url, + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: { + 'harmony_browser_page': 'pages/LaunchInAppPage' + }, + )) { + throw Exception('Could not launch $url'); + } + } + Future _launchInWebViewHeader(String url) async { + if (!await launcher.launch( + url, + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: { + 'my_header_key': 'my_header_value', + 'harmony_browser_page': 'pages/LaunchInAppPage' + }, + )) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInWebViewWithJavaScript(String url) async { + if (!await launcher.launch( + url, + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: { + 'harmony_browser_page': 'pages/LaunchInAppPage' + }, + )) { + throw Exception('Could not launch $url'); + } + } + + Future _launchInWebViewWithDomStorage(String url) async { + if (!await launcher.launch( + url, + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: { + 'harmony_browser_page': 'pages/LaunchInAppPage' + }, + )) { + throw Exception('Could not launch $url'); + } + } + + Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return const Text(''); + } + } + + Future _makePhoneCall(String phoneNumber) async { + // Use `Uri` to ensure that `phoneNumber` is properly URL-encoded. + // Just using 'tel:$phoneNumber' would create invalid URLs in some cases, + // such as spaces in the input, which would cause `launch` to fail on some + // platforms. + final Uri launchUri = Uri( + scheme: 'tel', + path: phoneNumber, + ); + await launcher.launch( + launchUri.toString(), + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: true, + headers: {}, + ); + } + + @override + Widget build(BuildContext context) { + // onPressed calls using this URL are not gated on a 'canLaunch' check + // because the assumption is that every device can launch a web URL. + const String toLaunch = 'https://www.cylog.org/headers/'; + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: ListView( + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: TextField( + onChanged: (String text) => _phone = text, + decoration: const InputDecoration( + hintText: 'Input the phone number to launch')), + ), + ElevatedButton( + onPressed: _hasCallSupport + ? () => setState(() { + _launched = _makePhoneCall(_phone); + }) + : null, + child: _hasCallSupport + ? const Text('Make phone call') + : const Text('Calling not supported'), + ), + const Padding( + padding: EdgeInsets.all(16.0), + child: Text(toLaunch), + ), + ElevatedButton( + /*onPressed: () => setState(() { + _launched = _launchInBrowser(toLaunch); + }),*/ + onPressed: null, + child: const Text('Launch in browser'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInBrowser(toLaunch); + }), + child: const Text('Launch in Ohos browser'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebView(toLaunch); + }), + child: const Text('Launch in app'), + ), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewHeader(toLaunch); + }), + child: const Text('Launch in web view (Custom headers)'), + ), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewWithJavaScript(toLaunch); + }), + child: const Text('Launch in app (JavaScript OFF)'), + ), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewWithDomStorage(toLaunch); + }), + child: const Text('Launch in app (DOM storage OFF)'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + ElevatedButton( + onPressed: () => setState(() { + _launched = _launchInWebView(toLaunch); + Timer(const Duration(seconds: 5), () { + launcher.closeWebView(); + }); + }), + child: const Text('Launch in app + close after 5 seconds'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + FutureBuilder(future: _launched, builder: _launchStatus), + ], + ), + ], + ), + ); + } +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/.gitignore b/packages/url_launcher/url_launcher_ohos/example/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6f7b4f89c49a6abadc383d9665d3b4c171d466bc --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/.gitignore @@ -0,0 +1,17 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test + +/entry/libs/arm64-v8a/libflutter.so +/entry/src/main/resources/rawfile/flutter_assets +**.har +**/oh-package-lock.json5 +BuildProfile.ets diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/app.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7db2955bee2fabc166c9e3bce24ea2d915d4209a --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "io.flutter.plugins.urllaunchersample", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/resources/base/element/string.json b/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..b26e28d922e796756d9115bcd67c1c458aac8946 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "sample" + } + ] +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/resources/base/media/app_icon.png b/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/packages/url_launcher/url_launcher_ohos/example/ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/build-profile.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bd8cc256c97d856b9511d19dcea7e77cb983ee95 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/build-profile.json5 @@ -0,0 +1,49 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "C:\\Users\\Administrator\\.ohos\\config\\default_ohos_DEIlyXGQI1RutsiQzdaJVsBV291dsUWCttcuKR-2n6k=.cer", + "storePassword": "0000001BF3977B63C96DA2B93A8B3FD24C65C2B8A49538C5D024B32A78C762C2F8C9F76AD023AF6DD53DE0", + "keyAlias": "debugKey", + "keyPassword": "0000001BA61CFF26765D1B254B4865DFA844D6C98AE4EC47C2E1B255BC236659F594CBB7E25CFFE77CD32A", + "profile": "C:\\Users\\Administrator\\.ohos\\config\\default_ohos_DEIlyXGQI1RutsiQzdaJVsBV291dsUWCttcuKR-2n6k=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\Administrator\\.ohos\\config\\default_ohos_DEIlyXGQI1RutsiQzdaJVsBV291dsUWCttcuKR-2n6k=.p12" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS" + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/.gitignore b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/build-profile.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4fee05f0045cc1ce1d18b7658dabfddc25c29022 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/build-profile.json5 @@ -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. + */ +{ + "apiType": 'stageMode', + "buildOption": { + }, + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/hvigorfile.ts b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..80e4ec5b81689f238c34614b167a0b9e9c83e8d9 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/hvigorfile.ts @@ -0,0 +1,2 @@ +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { hapTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/libs/arm64-v8a/libc++_shared.so b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/libs/arm64-v8a/libc++_shared.so new file mode 100644 index 0000000000000000000000000000000000000000..831c9353702073d45889352a4dafb93103d67d20 Binary files /dev/null and b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/libs/arm64-v8a/libc++_shared.so differ diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/oh-package.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..62367b73a69f90138553e834e11893d511a5193e --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/oh-package.json5 @@ -0,0 +1,13 @@ +{ + "license": "", + "devDependencies": {}, + "author": "", + "name": "entry", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": { + "integration_test": "file:../har/integration_test.har", + "url_launcher_ohos": "file:../har/url_launcher_ohos.har" + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..8670798491b6c5c0cf7911569973805c82d23530 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -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 { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos' +import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant'; + +export default class EntryAbility extends FlutterAbility { + configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + GeneratedPluginRegistrant.registerWith(flutterEngine); + } +} + diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/pages/Index.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..1125f9fdd95f4310a182c1c9e3680f37f73686c9 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,38 @@ +/* +* 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 { FlutterPage } from '@ohos/flutter_ohos' + +let storage = LocalStorage.getShared() +const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' + +@Entry(storage) +@Component +struct Index { + private context = getContext(this) as common.UIAbilityContext + @LocalStorageLink('viewId') viewId: string = ""; + + build() { + Column() { + FlutterPage({ viewId: this.viewId }) + } + } + + onBackPress(): boolean { + this.context.eventHub.emit(EVENT_BACK_PRESS) + return true + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/pages/LaunchInAppPage.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/pages/LaunchInAppPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..bbfce13e81a557d6500bff5129fb193e83044f18 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/ets/pages/LaunchInAppPage.ets @@ -0,0 +1,26 @@ +/* + * 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 { InAppBrowser } from 'url_launcher_ohos/src/main/ets/components/plugin/InAppBrowser'; + +@Entry +@Component +struct LaunchInAppPage { + + build() { + Row() { + InAppBrowser() + } + } +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/module.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..57f7efd11c7ebfb48a1080df38ac560b51382157 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/module.json5 @@ -0,0 +1,56 @@ +/* + * 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": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.INTERNET" + } + ] + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/element/color.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/element/string.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/media/icon.png b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/profile/main_pages.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..20c731259059a89ac3c87b95f0c8f521347503f9 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,6 @@ +{ + "src": [ + "pages/Index", + "pages/LaunchInAppPage" + ] +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/en_US/element/string.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/zh_CN/element/string.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..597ecf95e61d7e30367c22fe2f8638008361b044 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..7d3c2f1b51d74c9b77cd541df2a5db747a30c1d5 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets @@ -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 hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' + +export default function abilityTest() { + describe('ActsAbilityTest', function () { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(function () { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(function () { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(function () { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(function () { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain',0, function () { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc' + let b = 'b' + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b) + expect(a).assertEqual(a) + }) + }) +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/test/List.test.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..e234ecdeb68972cbec9b06614a498009d689540d --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,19 @@ +/* + * 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 abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..5725ed314a955468b9ed704b57d4013b72249c88 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -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 UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; +import Want from '@ohos.app.ability.Want'; +import AbilityConstant from '@ohos.app.ability.AbilityConstant'; + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..4976263a5179927d72bca9f6168513f116a49e8a --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,48 @@ +/* + * 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'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..040a3d0778ec7d7f9bbb28e55495ab37b6f31169 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.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 hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/module.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..6ccd7e53bab33f74bfb72d3667da37a905e02d6d --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/module.json5 @@ -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. + */ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/test/List.test.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..4340a28079c38279905241323020c76addf12f1f --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* + * 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest() +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/test/LocalUnit.test.ets b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..56331147662f5540114a4afa9cee1b8470eb0505 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* + * 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', function () { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(function () { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(function () { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(function () { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(function () { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, function () { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/hvigor/hvigor-config.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ba720309263e55532a56354c49cdbc813c391ff6 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,8 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "properties": { + "ohos.nativeResolver": false + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/hvigorfile.ts b/packages/url_launcher/url_launcher_ohos/example/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/packages/url_launcher/url_launcher_ohos/example/ohos/oh-package.json5 b/packages/url_launcher/url_launcher_ohos/example/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..657c309b35783d9a7df953469bb59d7ba4c6edf6 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/ohos/oh-package.json5 @@ -0,0 +1,21 @@ +{ + "modelVersion": "5.0.0", + "license": "", + "devDependencies": { + "@ohos/hypium": "1.0.6" + }, + "author": "", + "name": "sample", + "description": "Please describe the basic information.", + "main": "", + "version": "1.0.0", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + }, + "overrides": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "url_launcher_ohos": "file:./har/url_launcher_ohos.har", + "integration_test": "file:./har/integration_test.har", + "@ohos/flutter_module": "file:./entry" + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/example/pubspec.yaml b/packages/url_launcher/url_launcher_ohos/example/pubspec.yaml new file mode 100644 index 0000000000000000000000000000000000000000..658860ffcb0200ecbdf5ac0076724bf2ab138b73 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/example/pubspec.yaml @@ -0,0 +1,43 @@ +# 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: url_launcher_example +description: Demonstrates how to use the url_launcher plugin. +publish_to: none + +environment: + sdk: ^3.4.0 + flutter: ">=3.21.0" + +dependencies: + flutter: + sdk: flutter + url_launcher_ohos: + # When depending on this package from a real application you should use: + # url_launcher_ohos: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + url_launcher_platform_interface: ^2.3.1 + +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + mockito: 5.4.4 + plugin_platform_interface: ^2.1.7 + +flutter: + uses-material-design: true diff --git a/packages/url_launcher/url_launcher_ohos/integration_test/url_launcher_test.dart b/packages/url_launcher/url_launcher_ohos/integration_test/url_launcher_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..d7ae6d53e31a165f30c5e2d75c529f1d3d681dc8 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/integration_test/url_launcher_test.dart @@ -0,0 +1,74 @@ +/* + * 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 'dart:async'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('canLaunch', (WidgetTester _) async { + final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; + + expect(await launcher.canLaunch('randomstring'), false); + + // Generally all devices should have some default browser. + expect(await launcher.canLaunch('http://flutter.dev'), true); + + // sms:, tel:, and mailto: links may not be openable on every device, so + // aren't tested here. + }); + + testWidgets('launch and close', (WidgetTester _) async { + final UrlLauncherPlatform launcher = UrlLauncherPlatform.instance; + + // Setup fake http server. + final HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 0); + unawaited(server.forEach((HttpRequest request) { + if (request.uri.path == '/hello.txt') { + request.response.writeln('Hello, world.'); + } else { + fail('unexpected request: ${request.method} ${request.uri}'); + } + request.response.close(); + })); + // Https to avoid cleartext warning on ohos. + final String prefixUrl = 'https://${server.address.address}:${server.port}'; + final String primaryUrl = '$prefixUrl/hello.txt'; + + // Launch a url then close. + expect( + await launcher.launch(primaryUrl, + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: {}), + true); + await launcher.closeWebView(); + // Delay required to catch ohos side crashes in onDestroy. + // + // If this test flakes with an ohos crash during this delay the test + // should be considered failing because this integration test can have a + // false positive pass if the test closes before an onDestroy crash. + // See https://github.com/flutter/flutter/issues/126460 for more info. + await Future.delayed(const Duration(seconds: 5)); + }); +} diff --git a/packages/url_launcher/url_launcher_ohos/lib/src/messages.g.dart b/packages/url_launcher/url_launcher_ohos/lib/src/messages.g.dart new file mode 100644 index 0000000000000000000000000000000000000000..46e2ef8b0b71c400b8c678f9984c4b26900d58ca --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/lib/src/messages.g.dart @@ -0,0 +1,195 @@ +/* + * 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 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +/// Configuration options for an in-app WebView. +class WebViewOptions { + WebViewOptions({ + required this.enableJavaScript, + required this.enableDomStorage, + required this.headers, + }); + + bool enableJavaScript; + + bool enableDomStorage; + + Map headers; + + Object encode() { + return [ + enableJavaScript, + enableDomStorage, + headers, + ]; + } + + static WebViewOptions decode(Object result) { + result as List; + return WebViewOptions( + enableJavaScript: result[0]! as bool, + enableDomStorage: result[1]! as bool, + headers: (result[2] as Map?)!.cast(), + ); + } +} + +class _UrlLauncherApiCodec extends StandardMessageCodec { + const _UrlLauncherApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is WebViewOptions) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return WebViewOptions.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class UrlLauncherApi { + /// Constructor for [UrlLauncherApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + UrlLauncherApi({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = _UrlLauncherApiCodec(); + + /// Returns true if the URL can definitely be launched. + Future canLaunchUrl(String arg_url) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + /// Opens the URL externally, returning true if successful. + Future launchUrl( + String arg_url, Map arg_headers) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.launchUrl', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url, arg_headers]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + /// Opens the URL in an in-app WebView, returning true if it opens + /// successfully. + Future openUrlInWebView( + String arg_url, WebViewOptions arg_options) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_url, arg_options]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + /// Closes the view opened by [openUrlInSafariViewController]. + Future closeWebView() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.UrlLauncherApi.closeWebView', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } +} diff --git a/packages/url_launcher/url_launcher_ohos/lib/url_launcher_ohos.dart b/packages/url_launcher/url_launcher_ohos/lib/url_launcher_ohos.dart new file mode 100644 index 0000000000000000000000000000000000000000..e50d4870ebc30d840efe30c0e838c2a52c21ca85 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/lib/url_launcher_ohos.dart @@ -0,0 +1,109 @@ +/* + * 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 'package:flutter/foundation.dart' show visibleForTesting; +import 'package:flutter/services.dart'; +import 'package:url_launcher_platform_interface/link.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +import 'src/messages.g.dart'; + +/// An implementation of [UrlLauncherPlatform] for Ohos. +class UrlLauncherOhos extends UrlLauncherPlatform { + /// Creates a new plugin implementation instance. + UrlLauncherOhos({ + @visibleForTesting UrlLauncherApi? api, + }) : _hostApi = api ?? UrlLauncherApi(); + + final UrlLauncherApi _hostApi; + + /// Registers this class as the default instance of [UrlLauncherPlatform]. + static void registerWith() { + UrlLauncherPlatform.instance = UrlLauncherOhos(); + } + + @override + final LinkDelegate? linkDelegate = null; + + @override + Future canLaunch(String url) async { + final bool canLaunchSpecificUrl = await _hostApi.canLaunchUrl(url); + if (!canLaunchSpecificUrl) { + final String scheme = _getUrlScheme(url); + // canLaunch can return false when a custom application is registered to + // handle a web URL, but the caller doesn't have permission to see what + // that handler is. If that happens, try a web URL (with the same scheme + // variant, to be safe) that should not have a custom handler. If that + // returns true, then there is a browser, which means that there is + // at least one handler for the original URL. + if (scheme == 'http' || scheme == 'https') { + return _hostApi.canLaunchUrl('$scheme://flutter.dev'); + } + } + return canLaunchSpecificUrl; + } + + @override + Future closeWebView() { + return _hostApi.closeWebView(); + } + + // TODO(stuartmorgan): Implement launchUrl, and make this a passthrough + // to launchUrl. See also https://github.com/flutter/flutter/issues/66721 + @override + Future launch( + String url, { + required bool useSafariVC, + required bool useWebView, + required bool enableJavaScript, + required bool enableDomStorage, + required bool universalLinksOnly, + required Map headers, + String? webOnlyWindowName, + }) async { + final bool succeeded; + if (useWebView) { + succeeded = await _hostApi.openUrlInWebView( + url, + WebViewOptions( + enableJavaScript: enableJavaScript, + enableDomStorage: enableDomStorage, + headers: headers)); + } else { + succeeded = await _hostApi.launchUrl(url, headers); + } + // TODO(stuartmorgan): Remove this special handling as part of a + // breaking change to rework failure handling across all platform. The + // current behavior is backwards compatible with the previous Java error. + if (!succeeded) { + throw PlatformException( + code: 'ACTIVITY_NOT_FOUND', + message: 'No Activity found to handle intent { $url }'); + } + return succeeded; + } + + // Returns the part of [url] up to the first ':', or an empty string if there + // is no ':'. This deliberately does not use [Uri] to extract the scheme + // so that it works on strings that aren't actually valid URLs, since Ohos + // is very lenient about what it accepts for launching. + String _getUrlScheme(String url) { + final int schemeEnd = url.indexOf(':'); + if (schemeEnd == -1) { + return ''; + } + return url.substring(0, schemeEnd); + } +} diff --git a/packages/url_launcher/url_launcher_ohos/ohos/.gitignore b/packages/url_launcher/url_launcher_ohos/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6f7b4f89c49a6abadc383d9665d3b4c171d466bc --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/.gitignore @@ -0,0 +1,17 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test + +/entry/libs/arm64-v8a/libflutter.so +/entry/src/main/resources/rawfile/flutter_assets +**.har +**/oh-package-lock.json5 +BuildProfile.ets diff --git a/packages/url_launcher/url_launcher_ohos/ohos/build-profile.json5 b/packages/url_launcher/url_launcher_ohos/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8d5a27749b92674777c50631deea098b3bb2620b --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/build-profile.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. + */ +{ + "apiType": "stageMode", + "buildOption": { + }, + "targets": [ + { + "name": "default" + } + ] +} diff --git a/packages/url_launcher/url_launcher_ohos/ohos/hvigorfile.ts b/packages/url_launcher/url_launcher_ohos/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1a91468a73f05396b9e08abaabfe8717b73fbb4 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/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 { harTasks } from '@ohos/hvigor-ohos-plugin'; \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/ohos/index.ets b/packages/url_launcher/url_launcher_ohos/ohos/index.ets new file mode 100644 index 0000000000000000000000000000000000000000..85f1d8bc37ed1b89a5e3aa010e36cbbc685c2048 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/index.ets @@ -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. + */ + +import UrlLauncherPlugin from './src/main/ets/components/plugin/UrlLauncherPlugin'; +export default UrlLauncherPlugin; \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/ohos/oh-package.json5 b/packages/url_launcher/url_launcher_ohos/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..94dc5db632d530bee3333c90032a0ae073634686 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/oh-package.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. + */ +{ + "name": "url_launcher_ohos", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/InAppBrowser.ets b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/InAppBrowser.ets new file mode 100644 index 0000000000000000000000000000000000000000..d17d1ebba65012b0981d695fd374bf9339c4c7fa --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/InAppBrowser.ets @@ -0,0 +1,56 @@ +/* + * 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 web_webview from '@ohos.web.webview'; +import router from '@ohos.router'; +import { ValueType } from '@kit.ArkData'; + +@Entry +@Component +export struct InAppBrowser { + webviewController: web_webview.WebviewController = new web_webview.WebviewController() + + private url?: string; + private my_header_value?: string; + private enableJavaScript: boolean = true; + private enableDomStorage: boolean = true; + private flage: boolean = false; + aboutToAppear(){ + let params = router.getParams() as Map + this.url = params['url'] as string; + let options = params['options'] as Map; + this.enableJavaScript = options['enableJavaScript'] as boolean; + this.enableDomStorage = options['enableDomStorage'] as boolean; + this.my_header_value= params ['my_header_key'] as string; + } + build() { + Row() { + Web({ src: this.url, controller: this.webviewController }) + .javaScriptAccess(this.enableJavaScript) + .domStorageAccess(this.enableDomStorage) + .onRefreshAccessedHistory((event) => { + if (event && !this.flage) { + if (this.my_header_value != undefined) { + this.webviewController.loadUrl('https://www.cylog.org/headers/', [{ headerKey: "my_header_key", headerValue: this.my_header_value }]); + this.flage = true; + } else { + this.webviewController.loadUrl('https://www.cylog.org/headers/'); + this.flage = true; + } + } + }) + + }.width('100%').height('100%') + } +} diff --git a/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/Messages.ets b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/Messages.ets new file mode 100644 index 0000000000000000000000000000000000000000..181cb90b22ca99c4d63365b63e86b7ed196d3049 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/Messages.ets @@ -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 MessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/MessageCodec'; +import StandardMessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/StandardMessageCodec'; +import { BinaryMessenger } from '@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger'; +import { ByteBuffer } from '@ohos/flutter_ohos/src/main/ets/util/ByteBuffer'; +import Log from '@ohos/flutter_ohos/src/main/ets/util/Log'; + +const TAG = 'UrlLauncher' + +export class FlutterError extends Error { + /** The error code. */ + public code: string = ''; + + /** The error details. Must be a datatype supported by the api codec. */ + public details: ESObject | null; + + constructor(code: string, message: string, details: ESObject) { + super(message); + this.code = code; + this.details = details; + } +} + +export function wrapError(exception: Error): Array { + let errorList = new Array(); + if (exception instanceof FlutterError) { + let error = exception as FlutterError; + errorList.push(error.code); + errorList.push(error.message); + errorList.push(error.details); + } else { + errorList.push(exception.name); + errorList.push( + "Cause: " + exception.message + ", Stacktrace: " + exception.stack); + } + return errorList; +} + +/** + * Configuration options for an in-app WebView. + * + */ +export class WebViewOptions { + private enableJavaScript: boolean = false; + + public getEnableJavaScript(): boolean { + return this.enableJavaScript; + } + + public setEnableJavaScript(setterArg: boolean): void { + if (setterArg == null) { + throw new ReferenceError("Nonnull field \"enableJavaScript\" is null."); + } + this.enableJavaScript = setterArg; + } + + private enableDomStorage: boolean = false; + + public getEnableDomStorage(): boolean { + return this.enableDomStorage; + } + + public setEnableDomStorage(setterArg: boolean) { + if (setterArg == null) { + throw new ReferenceError("Nonnull field \"enableDomStorage\" is null."); + } + this.enableDomStorage = setterArg; + } + + private headers: Map = new Map(); + + public getHeaders(): Map { + return this.headers; + } + + public setHeaders(setterArg: Map) { + if (setterArg == null) { + throw new ReferenceError("Nonnull field \"headers\" is null."); + } + this.headers = setterArg; + } + + toList(): Array { + let toListResult = new Array(); + toListResult.push(this.enableJavaScript); + toListResult.push(this.enableDomStorage); + toListResult.push(this.headers); + return toListResult; + } + + static fromList(list: Array): WebViewOptions { + let result = new WebViewOptions(); + let enableJavaScript = list[0] as boolean; + result.setEnableJavaScript(enableJavaScript); + let enableDomStorage = list[1] as boolean; + result.setEnableDomStorage(enableDomStorage); + let headers = list[2]; + result.setHeaders(headers as Map); + return result; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + private WebViewOptions() {} +} + +export class UrlLauncherApiCodec extends StandardMessageCodec { + public static INSTANCE: UrlLauncherApiCodec = new UrlLauncherApiCodec(); + + private constructor() { + super(); + } + + readValueOfType(type: number, buffer: ByteBuffer): Object { + switch (type) { + case 128: + return WebViewOptions.fromList(super.readValue(buffer) as Array); + default: + return super.readValueOfType(type, buffer); + } + } + + writeValue(stream: ByteBuffer, value: ESObject): void { + if (value instanceof WebViewOptions) { + stream.writeInt8(128); + this.writeValue(stream, (value as WebViewOptions).toList()); + } else { + super.writeValue(stream, value); + } + } +} + +export interface UrlLauncherApi { + /** Returns true if the URL can definitely be launched. */ + canLaunchUrl(url: string): boolean; + + /** Opens the URL externally, returning true if successful. */ + launchUrl(url: string, headers: Map): boolean; + + /** Opens the URL in an in-app WebView, returning true if it opens successfully. */ + openUrlInWebView(url: string, options: WebViewOptions): boolean; + + /** Closes the view opened by [openUrlInSafariViewController]. */ + closeWebView(): boolean; + + /** The codec used by UrlLauncherApi. */ + getCodec(): MessageCodec; + + /** Sets up an instance of `UrlLauncherApi` to handle messages through the `binaryMessenger`. */ + setup(binaryMessenger: BinaryMessenger | null, api: UrlLauncherApi | null): ESObject; +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/UrlLauncher.ets b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/UrlLauncher.ets new file mode 100644 index 0000000000000000000000000000000000000000..b283373427d5361eeb703044d589e8528252f564 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/UrlLauncher.ets @@ -0,0 +1,402 @@ +/* + * 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 { BinaryMessenger } from '@ohos/flutter_ohos/src/main/ets/plugin/common/BinaryMessenger'; +import MessageCodec from '@ohos/flutter_ohos/src/main/ets/plugin/common/MessageCodec'; +import BasicMessageChannel, { Reply } from '@ohos/flutter_ohos/src/main/ets/plugin/common/BasicMessageChannel'; +import Want from '@ohos.app.ability.Want'; +import wantConstant from '@ohos.app.ability.wantConstant'; +import fileuri from '@ohos.file.fileuri'; +import { FlutterError, UrlLauncherApi, UrlLauncherApiCodec, WebViewOptions, wrapError } from './Messages'; +import common from '@ohos.app.ability.common'; +import Log from '@ohos/flutter_ohos/src/main/ets/util/Log'; +import call from '@ohos.telephony.call'; +import i18n from '@ohos.i18n'; +import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; +import bundleManager from '@ohos.bundle.bundleManager'; +import router from '@ohos.router' +import promptAction from '@ohos.promptAction'; +import { BusinessError } from '@kit.BasicServicesKit'; + +const TAG = 'UrlLauncher' + +class SendMessageParams { + telephone: string = ''; + telephoneFormat: string = ''; + contactName: string = ''; +} + +export class UrlLauncher implements UrlLauncherApi { + static LAUNCH_TYPE_TEL: string = 'tel:'; + static LAUNCH_TYPE_WEB_HTTP: string = 'http:'; + static LAUNCH_TYPE_WEB_HTTPS: string = 'https:'; + static LAUNCH_TYPE_MAILTO: string = 'mailto:'; + static LAUNCH_TYPE_SMS: string = 'sms:'; + static LAUNCH_TYPE_FILE: string = 'file:'; + + static MMS_BUNDLE_NAME = "com.ohos.mms"; + static MMS_ABILITY_NAME = "com.ohos.mms.MainAbility"; + static MMS_ENTITIES = 'entity.system.home'; + + private context: common.UIAbilityContext; + + constructor(ctx: common.UIAbilityContext) { + this.context = ctx; + } + + getPermission() { + let array: Array = [ + "ohos.permission.READ_MEDIA", + "ohos.permission.WRITE_MEDIA", + "ohos.permission.MEDIA_LOCATION", + "ohos.permission.GET_BUNDLE_INFO", + "ohos.permission.DISTRIBUTED_DATASYNC", + ]; + + let atManager = abilityAccessCtrl.createAtManager(); + //requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗 + atManager.requestPermissionsFromUser(this.context, array).then((data) => { + Log.d(TAG, "data type:" + typeof (data)); + Log.d(TAG, "data:" + data); + Log.d(TAG, "data permissions:" + data.permissions); + Log.d(TAG, "data result:" + data.authResults); + }, (err: Error) => { + Log.e(TAG,'Failed to start ability', JSON.stringify(err)); + }); + } + + canLaunchUrl(url: string): boolean { + let encodedUri = encodeURI(url); + // Trick:由于 sms、tel、file、mailto 用 bundleManager.canOpenLink 判断会报 17700055 错误,这里暂时保留 if 这段条件判断代码 + if (encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_TEL) || + encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_WEB_HTTP) || + encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_WEB_HTTPS) || + encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_MAILTO) || + encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_SMS) || + encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_FILE)) { + return true; + } + + try { + // 除了 sms、tel、file、mailto、http、https 开头的这六种 uri 之外的,用下面的方法判断是否支持打开。 + // 需要在 src/main/module.json5 的 querySchemes 中增加对应的头部信息,例如打开 "amapuri://route/plan/?did=xxx",module.json5 的样例:{ "module": { "querySchemes": [ "amapuri" ] } } + if (bundleManager.canOpenLink(encodedUri)) { + return true; + } + return false; + } catch (error) { + Log.e(TAG, 'canOpenLink error: ', JSON.stringify(error)); + return false; + } + } + + launchUrl(url: string, headers: Map): boolean { + if (!this.canLaunchUrl(url)) { + return false; + } + + this.ensureContext(); + let encodedUri = encodeURI(url); + Log.d(TAG, 'launchUrl. url:' + url + ' eurl:' + encodedUri); + if (encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_TEL)) { + return this.launchTel(url); + } else if (encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_WEB_HTTP) || + encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_WEB_HTTPS)) { + return this.launchWeb(url, headers); + } else if (encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_MAILTO)) { + return this.launchMail(url); + } else if (encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_SMS)) { + return this.launchSms(url); + } else if (encodedUri.startsWith(UrlLauncher.LAUNCH_TYPE_FILE)) { + return this.launchFile(url); + } else { + return this.launchOther(url); + } + } + + format(number: string) { + let regex: RegExp = new RegExp('/[\s]/g'); + let phoneNumber = number.replace(regex, ''); + let countryId = i18n.getSystemRegion(); + let phoneNumberFormat= new i18n.PhoneNumberFormat(countryId); + let isNumberValid:boolean = phoneNumberFormat.isValidNumber(phoneNumber); + let formatNumber = isNumberValid ? phoneNumberFormat.format(phoneNumber) : phoneNumber; + return formatNumber; + } + + launchSms(url: string): boolean { + let phoneNum = this.parseUrl(url, UrlLauncher.LAUNCH_TYPE_SMS); + let formatNum = this.format(phoneNum); + + let msgParam = new SendMessageParams(); + msgParam.telephone = phoneNum; + msgParam.telephoneFormat = formatNum; + + let params = new Array(); + params.push(msgParam); + + let actionData: ESObject = {}; + actionData.contactObjects = JSON.stringify(params); + actionData.pageFlag = 'conversation'; + let str : Want = { + 'bundleName': UrlLauncher.MMS_BUNDLE_NAME, + 'abilityName': UrlLauncher.MMS_ABILITY_NAME, + 'parameters': actionData, + 'entities': [ + UrlLauncher.MMS_ENTITIES, + ] + }; + Log.i(TAG, 'jumpToMms want: %s', JSON.stringify(params)); + this.context.startAbility(str).then(() => { + Log.i(TAG, 'jumpToMms success.'); + }).catch((error: ESObject) => { + Log.e(TAG, 'jumpToMms failed: %s', JSON.stringify(error)); + }) + return true + } + + launchTel(url: string): boolean { + Log.d(TAG, 'launchTel:' + url); + let phoneNum = this.parseUrl(url, UrlLauncher.LAUNCH_TYPE_TEL + ':'); + call.makeCall(phoneNum).then(() => { + Log.e(TAG, `launchTel success`); + }).catch((err: ESObject) => { + Log.e(TAG, `launchTel failed, error: ${JSON.stringify(err)}`); + }) + return true; + } + + // 只支持沙箱路径 + launchFile(url: string): boolean { + let fileUrl = this.parseUrl(url, UrlLauncher.LAUNCH_TYPE_FILE + '://'); + let filePath = this.context.filesDir + fileUrl; + // 将沙箱路径转换为uri + let uriFromPath = fileuri.getUriFromPath(filePath); + Log.d(TAG, 'launchFile:' + fileUrl + ' uriFromPath:' + uriFromPath); + let want: Want = { + flags: wantConstant.Flags.FLAG_AUTH_WRITE_URI_PERMISSION | wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION, + // 配置分享应用的隐式拉起规则 + action: 'ohos.want.action.viewData', + uri: uriFromPath, + } + this.context.startAbility(want) + .then(() => { + Log.i(TAG, 'Invoke getCurrentBundleStats succeeded.'); + }) + .catch((err: ESObject) => { + Log.e(TAG, `Invoke startAbility failed, code is ${err.code}, message is ${err.message}`); + }); + return true; + } + + launchWeb(url: string, headers: Map): boolean { + Log.d(TAG, 'launchWeb url:' + url); + let params: Record = {}; + + for (let val of headers) { + params[val[0]] = val[1] + } + + let want: Want = { + uri: url, + parameters: params, + entities: ['entity.system.browsable'], + action: 'ohos.want.action.viewData', + } + try { + this.context.startAbility(want) + } catch (e) { + Log.e(TAG, 'launchWeb failed. err:' + JSON.stringify(e)) + return false + } + return true; + } + + launchMail(url: string): boolean { + let mailAddress = this.parseUrl(url, UrlLauncher.LAUNCH_TYPE_MAILTO + ':'); + let want: Want = { + abilityName: 'PhoneAbility', + bundleName: 'com.huawei.hmos.email', + } + try { + this.context.startAbility(want) + } catch (e) { + Log.e(TAG, 'launchMail failed. err:' + JSON.stringify(e)) + return false + } + return true; + } + + launchOther(url: string): boolean { + let want: Want = { + uri: url, + } + try { + this.context.startAbility(want) + } catch (e) { + Log.e(TAG, 'launchOther failed. err:' + JSON.stringify(e)) + return false + } + return true; + } + + parseUrl(url: string, prefix: string): string { + if (url == null || url == undefined) { + return '' + } + return url.replace(prefix, ''); + } + + private ensureContext(): void { + if (this.context == null) { + throw new FlutterError( + "NO_ACTIVITY", "Launching a URL requires a foreground activity.", null); + } + } + + openUrlInWebView(url: string, options: WebViewOptions): boolean { + let headers = options.getHeaders(); + let my_header_key = headers.get('my_header_key'); + if (!headers.has('harmony_browser_page')) { + promptAction.showToast({ + message: '请在 launch 函数的 headers 参数中添加 harmony_browser_page 和对应的值,并在鸿蒙工程中增加对应的页面和配置路由', + }); + return false; + } + let harmonyBrowserPage = headers.get('harmony_browser_page'); + router.pushUrl({ url: harmonyBrowserPage, params: {'url': url, 'options': options,'my_header_key':my_header_key} }).catch((error: BusinessError) => { + Log.e(TAG, 'Failed to open url: ', harmonyBrowserPage, ', Error: ', JSON.stringify(error)); + if (error.code == 100002) { + promptAction.showToast({ + message: harmonyBrowserPage + ' 页面不存在,或者没有在 main_pages.json 中声明', + }); + } + }); + return true; + } + + closeWebView(): boolean { + throw new Error('Method not implemented.'); + } + + getCodec(): MessageCodec { + return UrlLauncherApiCodec.INSTANCE; + } + + setup(binaryMessenger: BinaryMessenger, api: UrlLauncherApi) { + { + if (binaryMessenger == null) { + return; + } + let channel: BasicMessageChannel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.canLaunchUrl", this.getCodec()); + Log.d(TAG, 'setup launchUrl') + if (api != null && api != undefined) { + Log.d(TAG, 'setMessageHandler') + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + let wrapped = new Array(); + let args = msg as Array; + let urlArg = args[0] as string; + try { + let output = api.canLaunchUrl(urlArg); + wrapped.push(output); + } catch (err) { + let errs = wrapError(err); + wrapped = errs; + } + reply.reply(wrapped) + } + }); + } else { + channel.setMessageHandler(null) + } + } + { + let channel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.launchUrl", this.getCodec()); + Log.d(TAG, 'setup launchUrl') + if (api != null && api != undefined) { + Log.d(TAG, 'setMessageHandler') + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + let wrapped = new Array(); + let args = msg as Array; + let urlArg = args[0] as string; + let headersArg = args[1] as Map; + try { + let output = api.launchUrl(urlArg, headersArg); + wrapped.push(output); + } catch (err) { + let errs = wrapError(err); + wrapped = errs; + } + reply.reply(wrapped) + } + }); + } else { + channel.setMessageHandler(null) + } + } + { + let channel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.openUrlInWebView", this.getCodec()); + Log.d(TAG, 'setup launchUrl') + if (api != null && api != undefined) { + Log.d(TAG, 'setMessageHandler') + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + let wrapped = new Array(); + let args = msg as Array; + let urlArg = args[0] as string; + let optionsArg = args[1] as WebViewOptions; + try { + let output = api.openUrlInWebView(urlArg, optionsArg); + wrapped.push(output); + } catch (err) { + let errs = wrapError(err); + wrapped = errs; + } + reply.reply(wrapped) + } + }); + } else { + channel.setMessageHandler(null) + } + } + { + let channel = new BasicMessageChannel( + binaryMessenger, "dev.flutter.pigeon.UrlLauncherApi.closeWebView", this.getCodec()); + Log.d(TAG, 'setup launchUrl') + if (api != null && api != undefined) { + Log.d(TAG, 'setMessageHandler') + channel.setMessageHandler({ + onMessage(msg: ESObject, reply: Reply): void { + let wrapped = new Array(); + try { + let output = api.closeWebView(); + wrapped.push(output); + } catch (err) { + let errs = wrapError(err); + wrapped = errs; + } + reply.reply(wrapped) + } + }); + } else { + channel.setMessageHandler(null) + } + } + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/UrlLauncherPlugin.ets b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/UrlLauncherPlugin.ets new file mode 100644 index 0000000000000000000000000000000000000000..f4163876e20e74cb6d16a173992393702fcecfaf --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/src/main/ets/components/plugin/UrlLauncherPlugin.ets @@ -0,0 +1,57 @@ +/* + * 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 AbilityAware from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityAware'; +import { + AbilityPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding'; +import { + FlutterPlugin, + FlutterPluginBinding +} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin'; +import { UrlLauncherApi } from './Messages'; +import { UrlLauncher } from './UrlLauncher'; + +const TAG = "UrlLauncherPlugin" + +export default class UrlLauncherPlugin implements FlutterPlugin, AbilityAware { + + private pluginBinding: FlutterPluginBinding | null = null; + private urlLauncherApi: UrlLauncherApi | null = null; + + getUniqueClassName(): string { + return "UrlLauncherPlugin" + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + console.debug(TAG, 'onAttachedToAbility ') + this.urlLauncherApi = new UrlLauncher(binding.getAbility().context); + this.urlLauncherApi.setup( + this.pluginBinding?.getBinaryMessenger() ?? null, this.urlLauncherApi); + } + onDetachedFromAbility(): void { + this.urlLauncherApi?.setup(null, null); + } + + onAttachedToEngine(binding: FlutterPluginBinding): void { + console.debug(TAG, 'onAttachedToEngine') + this.pluginBinding = binding; + } + + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + this.urlLauncherApi?.setup(this.pluginBinding?.getBinaryMessenger() ?? null, null); + this.pluginBinding = null; + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/ohos/src/main/module.json5 b/packages/url_launcher/url_launcher_ohos/ohos/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5bedf2151d21a4fb66083c1b8191f2bd3d40fa66 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/ohos/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": "url_launcher_ohos", + "type": "har", + "deviceTypes": [ + "default", + "tablet" + ] + } +} \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_ohos/pubspec.yaml b/packages/url_launcher/url_launcher_ohos/pubspec.yaml new file mode 100644 index 0000000000000000000000000000000000000000..aac2cf8cd71538ee97289a3120dfc905677f079b --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/pubspec.yaml @@ -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. + +name: url_launcher_ohos +description: Ohos implementation of the url_launcher plugin. +repository: https://gitee.com/openharmony-sig/flutter_packages/tree/master/packages/url_launcher/url_launcher_ohos +issue_tracker: https://gitee.com/openharmony-sig/flutter_packages/issues +version: 6.3.3 +environment: + sdk: ^3.4.0 + flutter: ">=3.21.0" + +flutter: + plugin: + implements: url_launcher + platforms: + ohos: + package: io.flutter.plugins.urllauncher + pluginClass: UrlLauncherPlugin + dartPluginClass: UrlLauncherOhos + +dependencies: + flutter: + sdk: flutter + url_launcher_platform_interface: ^2.3.1 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: 5.4.4 + pigeon: ^10.0.0 + plugin_platform_interface: ^2.1.7 + test: ^1.16.3 + +topics: + - links + - os-integration + - url-launcher + - urls diff --git a/packages/url_launcher/url_launcher_ohos/test/url_launcher_ohos_test.dart b/packages/url_launcher/url_launcher_ohos/test/url_launcher_ohos_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..8b1c52e2f7050eef4c9bed516984a53e90aa19c1 --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/test/url_launcher_ohos_test.dart @@ -0,0 +1,269 @@ +/* + * 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 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:url_launcher_ohos/src/messages.g.dart'; +import 'package:url_launcher_ohos/url_launcher_ohos.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + late _FakeUrlLauncherApi api; + + setUp(() { + api = _FakeUrlLauncherApi(); + }); + + test('registers instance', () { + .registerWith(); + expect(UrlLauncherPlatform.instance, isA<>()); + }); + + group('canLaunch', () { + test('returns true', () async { + final launcher = (api: api); + final bool canLaunch = await launcher.canLaunch('http://example.com/'); + + expect(canLaunch, true); + }); + + test('returns false', () async { + final launcher = (api: api); + final bool canLaunch = await launcher.canLaunch('unknown://scheme'); + + expect(canLaunch, false); + }); + + test('checks a generic URL if an http URL returns false', () async { + final launcher = (api: api); + final bool canLaunch = await launcher + .canLaunch('http://${_FakeUrlLauncherApi.specialHandlerDomain}'); + + expect(canLaunch, true); + }); + + test('checks a generic URL if an https URL returns false', () async { + final launcher = (api: api); + final bool canLaunch = await launcher + .canLaunch('https://${_FakeUrlLauncherApi.specialHandlerDomain}'); + + expect(canLaunch, true); + }); + }); + + group('launch without webview', () { + test('calls through', () async { + final launcher = (api: api); + final bool launched = await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect(launched, true); + expect(api.usedWebView, false); + expect(api.passedWebViewOptions?.headers, isEmpty); + }); + + test('passes headers', () async { + final launcher = (api: api); + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {'key': 'value'}, + ); + expect(api.passedWebViewOptions?.headers.length, 1); + expect(api.passedWebViewOptions?.headers['key'], 'value'); + }); + + test('passes through no-activity exception', () async { + final launcher = (api: api); + await expectLater( + launcher.launch( + 'noactivity://', + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA())); + }); + + test('throws if there is no handling activity', () async { + final launcher = (api: api); + await expectLater( + launcher.launch( + 'unknown://scheme', + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); + }); + }); + + group('launch with webview', () { + test('calls through', () async { + final launcher = (api: api); + final bool launched = await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect(launched, true); + expect(api.usedWebView, true); + expect(api.passedWebViewOptions?.enableDomStorage, false); + expect(api.passedWebViewOptions?.enableJavaScript, false); + expect(api.passedWebViewOptions?.headers, isEmpty); + }); + + test('passes enableJavaScript to webview', () async { + final launcher = (api: api); + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: true, + enableJavaScript: true, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + + expect(api.passedWebViewOptions?.enableJavaScript, true); + }); + + test('passes enableDomStorage to webview', () async { + final launcher = (api: api); + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: true, + universalLinksOnly: false, + headers: const {}, + ); + + expect(api.passedWebViewOptions?.enableDomStorage, true); + }); + + test('passes through no-activity exception', () async { + final launcher = (api: api); + await expectLater( + launcher.launch( + 'noactivity://scheme', + useSafariVC: false, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA())); + }); + + test('throws if there is no handling activity', () async { + final launcher = (api: api); + await expectLater( + launcher.launch( + 'unknown://scheme', + useSafariVC: false, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ), + throwsA(isA().having( + (PlatformException e) => e.code, 'code', 'ACTIVITY_NOT_FOUND'))); + }); + }); + + group('closeWebView', () { + test('calls through', () async { + final launcher = (api: api); + await launcher.closeWebView(); + + expect(api.closed, true); + }); + }); +} + +/// A fake implementation of the host API that reacts to specific schemes. +/// +/// See _launch for the behaviors. +class _FakeUrlLauncherApi implements UrlLauncherApi { + WebViewOptions? passedWebViewOptions; + bool? usedWebView; + bool? closed; + + /// A domain that will be treated as having no handler, even for http(s). + static String specialHandlerDomain = 'special.handler.domain'; + + @override + Future canLaunchUrl(String url) async { + return _launch(url); + } + + @override + Future launchUrl(String url, Map headers) async { + passedWebViewOptions = WebViewOptions( + enableJavaScript: false, enableDomStorage: false, headers: headers); + usedWebView = false; + return _launch(url); + } + + @override + Future closeWebView() async { + closed = true; + } + + @override + Future openUrlInWebView(String url, WebViewOptions options) async { + passedWebViewOptions = options; + usedWebView = true; + return _launch(url); + } + + bool _launch(String url) { + final String scheme = url.split(':')[0]; + switch (scheme) { + case 'http': + case 'https': + return !url.contains(specialHandlerDomain); + case 'noactivity': + throw PlatformException(code: 'NO_ACTIVITY'); + default: + return false; + } + } +} diff --git a/packages/url_launcher/url_launcher_ohos/test_driver/integration_test.dart b/packages/url_launcher/url_launcher_ohos/test_driver/integration_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..c20ad8aee123a1893e742c989cdb70daae37cd6a --- /dev/null +++ b/packages/url_launcher/url_launcher_ohos/test_driver/integration_test.dart @@ -0,0 +1,18 @@ +/* + * 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 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver();