diff --git a/.ci/Dockerfile-LinuxDesktop b/.ci/Dockerfile-LinuxDesktop index 75308c4aa400..63e4516e26fc 100644 --- a/.ci/Dockerfile-LinuxDesktop +++ b/.ci/Dockerfile-LinuxDesktop @@ -19,3 +19,13 @@ RUN sudo apt-get update && sudo apt-get install -y google-cloud-sdk && \ RUN sudo apt-get install -y xvfb libegl1-mesa # Install Linux desktop build tool requirements. RUN sudo apt-get install -y clang cmake ninja-build file pkg-config +# Install necessary libraries. +RUN sudo apt-get install -y libgtk-3-dev + +# Add repo for Google Chrome and install it, for url_launcher tests. +RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - +RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list +RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends google-chrome-stable +# Make it the default so http: has a handler. +RUN sudo apt-get install -y xdg-utils +RUN xdg-settings set default-web-browser google-chrome.desktop diff --git a/AUTHORS b/AUTHORS index b27c156188f8..51345c9a3481 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,4 +56,6 @@ Giancarlo Rocha Ryo Miyake Théo Champion Kazuki Yamaguchi -Eitan Schwartz \ No newline at end of file +Eitan Schwartz +Chris Rutkowski +Juan Alvarez diff --git a/README.md b/README.md index aeb03ffa23ac..026dd9d7739b 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,18 @@ These are the available plugins in this repository. | [camera](./packages/camera/) | [![pub package](https://img.shields.io/pub/v/camera.svg)](https://pub.dev/packages/camera) | | [connectivity](./packages/connectivity/) | [![pub package](https://img.shields.io/pub/v/connectivity.svg)](https://pub.dev/packages/connectivity) | | [device_info](./packages/device_info/) | [![pub package](https://img.shields.io/pub/v/device_info.svg)](https://pub.dev/packages/device_info) | +| [e2e](./packages/e2e/) | [![pub package](https://img.shields.io/pub/v/e2e.svg)](https://pub.dev/packages/e2e) | +| [espresso](./packages/espresso/) | [![pub package](https://img.shields.io/pub/v/espresso.svg)](https://pub.dev/packages/espresso) | +| [flutter_plugin_android_lifecycle](./packages/flutter_plugin_android_lifecycle/) | [![pub package](https://img.shields.io/pub/v/flutter_plugin_android_lifecycle.svg)](https://pub.dev/packages/flutter_plugin_android_lifecycle) | | [google_maps_flutter](./packages/google_maps_flutter) | [![pub package](https://img.shields.io/pub/v/google_maps_flutter.svg)](https://pub.dev/packages/google_maps_flutter) | | [google_sign_in](./packages/google_sign_in/) | [![pub package](https://img.shields.io/pub/v/google_sign_in.svg)](https://pub.dev/packages/google_sign_in) | | [image_picker](./packages/image_picker/) | [![pub package](https://img.shields.io/pub/v/image_picker.svg)](https://pub.dev/packages/image_picker) | | [in_app_purchase](./packages/in_app_purchase/) | [![pub package](https://img.shields.io/pub/v/in_app_purchase.svg)](https://pub.dev/packages/in_app_purchase) | +| [ios_platform_images](./packages/ios_platform_images/) | [![pub package](https://img.shields.io/pub/v/ios_platform_images.svg)](https://pub.dev/packages/ios_platform_images) | | [local_auth](./packages/local_auth/) | [![pub package](https://img.shields.io/pub/v/local_auth.svg)](https://pub.dev/packages/local_auth) | | [package_info](./packages/package_info/) | [![pub package](https://img.shields.io/pub/v/package_info.svg)](https://pub.dev/packages/package_info) | | [path_provider](./packages/path_provider/) | [![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dev/packages/path_provider) | +| [plugin_platform_interface](./packages/plugin_platform_interface/) | [![pub package](https://img.shields.io/pub/v/plugin_platform_interface.svg)](https://pub.dev/packages/plugin_platform_interface) | | [quick_actions](./packages/quick_actions/) | [![pub package](https://img.shields.io/pub/v/quick_actions.svg)](https://pub.dev/packages/quick_actions) | | [sensors](./packages/sensors/) | [![pub package](https://img.shields.io/pub/v/sensors.svg)](https://pub.dev/packages/sensors) | | [share](./packages/share/) | [![pub package](https://img.shields.io/pub/v/share.svg)](https://pub.dev/packages/share) | diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index d59cb71758ca..43e5f463604c 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.8+3 + +* Fix bug in usage example in README.md + ## 0.5.8+2 * Post-v2 embedding cleanups. diff --git a/packages/camera/README.md b/packages/camera/README.md index 4c23236cd0aa..fbd73929e6de 100644 --- a/packages/camera/README.md +++ b/packages/camera/README.md @@ -53,6 +53,7 @@ import 'package:camera/camera.dart'; List cameras; Future main() async { + WidgetsFlutterBinding.ensureInitialized(); cameras = await availableCameras(); runApp(CameraApp()); } diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 6cb514765e2d..a1ce42370191 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.5.8+2 +version: 0.5.8+3 homepage: https://github.com/flutter/plugins/tree/master/packages/camera diff --git a/packages/connectivity/connectivity/CHANGELOG.md b/packages/connectivity/connectivity/CHANGELOG.md index 9325f1a7868f..3bb777104c0e 100644 --- a/packages/connectivity/connectivity/CHANGELOG.md +++ b/packages/connectivity/connectivity/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.9 + +* Add support for `web` (by endorsing `connectivity_for_web` 0.3.0) + ## 0.4.8+6 * Update lower bound of dart dependency to 2.1.0. diff --git a/packages/connectivity/connectivity/example/lib/main.dart b/packages/connectivity/connectivity/example/lib/main.dart index 4ad30972679a..f8486165fa70 100644 --- a/packages/connectivity/connectivity/example/lib/main.dart +++ b/packages/connectivity/connectivity/example/lib/main.dart @@ -91,7 +91,7 @@ class _MyHomePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Plugin example app'), + title: const Text('Connectivity example app'), ), body: Center(child: Text('Connection Status: $_connectionStatus')), ); @@ -103,7 +103,7 @@ class _MyHomePageState extends State { String wifiName, wifiBSSID, wifiIP; try { - if (Platform.isIOS) { + if (!kIsWeb && Platform.isIOS) { LocationAuthorizationStatus status = await _connectivity.getLocationServiceAuthorization(); if (status == LocationAuthorizationStatus.notDetermined) { @@ -125,7 +125,7 @@ class _MyHomePageState extends State { } try { - if (Platform.isIOS) { + if (!kIsWeb && Platform.isIOS) { LocationAuthorizationStatus status = await _connectivity.getLocationServiceAuthorization(); if (status == LocationAuthorizationStatus.notDetermined) { diff --git a/packages/connectivity/connectivity/example/web/favicon.png b/packages/connectivity/connectivity/example/web/favicon.png new file mode 100644 index 000000000000..8aaa46ac1ae2 Binary files /dev/null and b/packages/connectivity/connectivity/example/web/favicon.png differ diff --git a/packages/connectivity/connectivity/example/web/icons/Icon-192.png b/packages/connectivity/connectivity/example/web/icons/Icon-192.png new file mode 100644 index 000000000000..b749bfef0747 Binary files /dev/null and b/packages/connectivity/connectivity/example/web/icons/Icon-192.png differ diff --git a/packages/connectivity/connectivity/example/web/icons/Icon-512.png b/packages/connectivity/connectivity/example/web/icons/Icon-512.png new file mode 100644 index 000000000000..88cfd48dff11 Binary files /dev/null and b/packages/connectivity/connectivity/example/web/icons/Icon-512.png differ diff --git a/packages/connectivity/connectivity/example/web/index.html b/packages/connectivity/connectivity/example/web/index.html new file mode 100644 index 000000000000..9b7a438f823a --- /dev/null +++ b/packages/connectivity/connectivity/example/web/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + example + + + + + + + + diff --git a/packages/connectivity/connectivity/example/web/manifest.json b/packages/connectivity/connectivity/example/web/manifest.json new file mode 100644 index 000000000000..8c012917dab7 --- /dev/null +++ b/packages/connectivity/connectivity/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/packages/connectivity/connectivity/pubspec.yaml b/packages/connectivity/connectivity/pubspec.yaml index 9aaa2620f82c..c7ef8fe6e723 100644 --- a/packages/connectivity/connectivity/pubspec.yaml +++ b/packages/connectivity/connectivity/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/c # 0.4.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.4.8+6 +version: 0.4.9 flutter: plugin: @@ -17,13 +17,16 @@ flutter: pluginClass: FLTConnectivityPlugin macos: default_package: connectivity_macos + web: + default_package: connectivity_for_web dependencies: flutter: sdk: flutter - meta: "^1.0.5" + meta: ^1.0.5 connectivity_platform_interface: ^1.0.2 connectivity_macos: ^0.1.0 + connectivity_for_web: ^0.3.0 dev_dependencies: flutter_test: diff --git a/packages/connectivity/connectivity_for_web/.gitignore b/packages/connectivity/connectivity_for_web/.gitignore new file mode 100644 index 000000000000..d7dee828a6b9 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ +lib/generated_plugin_registrant.dart diff --git a/packages/connectivity/connectivity_for_web/.metadata b/packages/connectivity/connectivity_for_web/.metadata new file mode 100644 index 000000000000..23eb55ba6da2 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 52ee8a6c6565cd421dfa32042941eb40691f4746 + channel: master + +project_type: plugin diff --git a/packages/connectivity/connectivity_for_web/CHANGELOG.md b/packages/connectivity/connectivity_for_web/CHANGELOG.md new file mode 100644 index 000000000000..83dc386a0314 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/CHANGELOG.md @@ -0,0 +1,15 @@ +## 0.3.1 + +* Use NetworkInformation API from dart:html, instead of the JS-interop version. + +## 0.3.0 + +* Rename from "experimental_connectivity_web" to "connectivity_for_web", and move to flutter/plugins master. + +## 0.2.0 + +* Add fallback on dart:html for browsers where NetworkInformationAPI is not supported. + +## 0.1.0 + +* Initial release. diff --git a/packages/connectivity/connectivity_for_web/LICENSE b/packages/connectivity/connectivity_for_web/LICENSE new file mode 100644 index 000000000000..4da9688730d1 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/LICENSE @@ -0,0 +1,26 @@ +Copyright 2016, the Flutter project 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/connectivity/connectivity_for_web/README.md b/packages/connectivity/connectivity_for_web/README.md new file mode 100644 index 000000000000..66efc49fc840 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/README.md @@ -0,0 +1,62 @@ +# connectivity_for_web + +A web implementation of [connectivity](https://pub.dev/connectivity/connectivity). Currently this package uses an experimental API, with a fallback to dart:html, so not all features may be available to all browsers. + +## Usage + +### Import the package + +This package is a non-endorsed implementation of `connectivity` for the web platform, so you need to modify your `pubspec.yaml` to use it: + +```yaml +... +dependencies: + ... + connectivity: ^0.4.9 + connectivity_for_web: ^0.3.0 + ... +... +``` + +## Example + +Find the example wiring in the [Google sign-in example application](https://github.com/ditman/plugins/blob/connectivity-web/packages/connectivity/connectivity/example/lib/main.dart). + +## Limitations on the web platform + +In order to retrieve information about the quality/speed of a browser's connection, the web implementation of the `connectivity` plugin uses the browser's [**NetworkInformation** Web API](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation), which as of this writing (June 2020) is still "experimental", and not available in all browsers: + +![Data on support for the netinfo feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/netinfo.png) + +On desktop browsers, this API only returns a very broad set of connectivity statuses (One of `'slow-2g', '2g', '3g', or '4g'`), and may *not* provide a Stream of changes. Firefox still hasn't enabled this feature by default. + +**Fallback to `navigator.onLine`** + +For those browsers where the NetworkInformation Web API is not available, the plugin falls back to the [**NavigatorOnLine** Web API](https://developer.mozilla.org/en-US/docs/Web/API/NavigatorOnLine), which is more broadly supported: + +![Data on support for the online-status feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/online-status.png) + + +The NavigatorOnLine API is [provided by `dart:html`](https://api.dart.dev/stable/2.7.2/dart-html/Navigator/onLine.html), and only supports a boolean connectivity status (either online or offline), with no network speed information. In those cases the plugin will return either `wifi` (when the browser is online) or `none` (when it's not). + +Other than the approximate "downlink" speed, where available, and due to security and privacy concerns, **no Web browser will provide** any specific information about the actual network your users' device is connected to, like **the SSID on a Wi-Fi, or the MAC address of their device.** + +## Contributions and Testing + +Tests are crucial to contributions to this package. All new contributions should be reasonably tested. + +In order to run tests in this package, do: + +``` +cd test +flutter run -d chrome +``` + +All contributions to this package are welcome. Read the [Contributing to Flutter Plugins](https://github.com/flutter/plugins/blob/master/CONTRIBUTING.md) guide to get started. + +## Issues and feedback + +Please file an [issue](https://github.com/ditman/plugins/issues/new) +to send feedback or report a bug. + +**Thank you!** diff --git a/packages/connectivity/connectivity_for_web/ios/connectivity_for_web.podspec b/packages/connectivity/connectivity_for_web/ios/connectivity_for_web.podspec new file mode 100644 index 000000000000..75b891c56533 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/ios/connectivity_for_web.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint connectivity_web.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'connectivity_for_web' + s.version = '0.1.0' + s.summary = 'No-op implementation of connectivity web plugin to avoid build issues on iOS' + s.description = <<-DESC +temp fake connectivity_web plugin + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_for_web' + s.license = { :file => '../LICENSE' } + s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.swift_version = '5.0' +end diff --git a/packages/connectivity/connectivity_for_web/lib/connectivity_for_web.dart b/packages/connectivity/connectivity_for_web/lib/connectivity_for_web.dart new file mode 100644 index 000000000000..fd061a878867 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/lib/connectivity_for_web.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:connectivity_platform_interface/connectivity_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'src/network_information_api_connectivity_plugin.dart'; +import 'src/dart_html_connectivity_plugin.dart'; + +/// The web implementation of the ConnectivityPlatform of the Connectivity plugin. +class ConnectivityPlugin extends ConnectivityPlatform { + /// Factory method that initializes the connectivity plugin platform with an instance + /// of the plugin for the web. + static void registerWith(Registrar registrar) { + if (NetworkInformationApiConnectivityPlugin.isSupported()) { + ConnectivityPlatform.instance = NetworkInformationApiConnectivityPlugin(); + } else { + ConnectivityPlatform.instance = DartHtmlConnectivityPlugin(); + } + } + + // The following are completely unsupported methods on the web platform. + + // Creates an "unsupported_operation" PlatformException for a given `method` name. + Object _unsupported(String method) { + return PlatformException( + code: 'UNSUPPORTED_OPERATION', + message: '$method() is not supported on the web platform.', + ); + } + + /// Obtains the wifi name (SSID) of the connected network + @override + Future getWifiName() { + throw _unsupported('getWifiName'); + } + + /// Obtains the wifi BSSID of the connected network. + @override + Future getWifiBSSID() { + throw _unsupported('getWifiBSSID'); + } + + /// Obtains the IP address of the connected wifi network + @override + Future getWifiIP() { + throw _unsupported('getWifiIP'); + } + + /// Request to authorize the location service (Only on iOS). + @override + Future requestLocationServiceAuthorization({ + bool requestAlwaysLocationUsage = false, + }) { + throw _unsupported('requestLocationServiceAuthorization'); + } + + /// Get the current location service authorization (Only on iOS). + @override + Future getLocationServiceAuthorization() { + throw _unsupported('getLocationServiceAuthorization'); + } +} diff --git a/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart b/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart new file mode 100644 index 000000000000..5caa5679d6ad --- /dev/null +++ b/packages/connectivity/connectivity_for_web/lib/src/dart_html_connectivity_plugin.dart @@ -0,0 +1,34 @@ +import 'dart:async'; +import 'dart:html' as html show window; + +import 'package:connectivity_platform_interface/connectivity_platform_interface.dart'; +import 'package:connectivity_for_web/connectivity_for_web.dart'; + +/// The web implementation of the ConnectivityPlatform of the Connectivity plugin. +class DartHtmlConnectivityPlugin extends ConnectivityPlugin { + /// Checks the connection status of the device. + @override + Future checkConnectivity() async { + return html.window.navigator.onLine + ? ConnectivityResult.wifi + : ConnectivityResult.none; + } + + StreamController _connectivityResult; + + /// Returns a Stream of ConnectivityResults changes. + @override + Stream get onConnectivityChanged { + if (_connectivityResult == null) { + _connectivityResult = StreamController(); + // Fallback to dart:html window.onOnline / window.onOffline + html.window.onOnline.listen((event) { + _connectivityResult.add(ConnectivityResult.wifi); + }); + html.window.onOffline.listen((event) { + _connectivityResult.add(ConnectivityResult.none); + }); + } + return _connectivityResult.stream; + } +} diff --git a/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart b/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart new file mode 100644 index 000000000000..99bac2ab8d30 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/lib/src/network_information_api_connectivity_plugin.dart @@ -0,0 +1,61 @@ +import 'dart:async'; +import 'dart:html' as html show window, NetworkInformation; +import 'dart:js'; +import 'dart:js_util'; + +import 'package:connectivity_platform_interface/connectivity_platform_interface.dart'; +import 'package:connectivity_for_web/connectivity_for_web.dart'; +import 'package:flutter/foundation.dart'; + +import 'utils/connectivity_result.dart'; + +/// The web implementation of the ConnectivityPlatform of the Connectivity plugin. +class NetworkInformationApiConnectivityPlugin extends ConnectivityPlugin { + final html.NetworkInformation _networkInformation; + + /// A check to determine if this version of the plugin can be used. + static bool isSupported() => html.window.navigator.connection != null; + + /// The constructor of the plugin. + NetworkInformationApiConnectivityPlugin() + : this.withConnection(html.window.navigator.connection); + + /// Creates the plugin, with an override of the NetworkInformation object. + @visibleForTesting + NetworkInformationApiConnectivityPlugin.withConnection( + html.NetworkInformation connection) + : _networkInformation = connection; + + /// Checks the connection status of the device. + @override + Future checkConnectivity() async { + return networkInformationToConnectivityResult(_networkInformation); + } + + StreamController _connectivityResultStreamController; + Stream _connectivityResultStream; + + /// Returns a Stream of ConnectivityResults changes. + @override + Stream get onConnectivityChanged { + if (_connectivityResultStreamController == null) { + _connectivityResultStreamController = + StreamController(); + setProperty(_networkInformation, 'onchange', allowInterop((_) { + _connectivityResultStreamController + .add(networkInformationToConnectivityResult(_networkInformation)); + })); + // TODO: Implement the above with _networkInformation.onChange: + // _networkInformation.onChange.listen((_) { + // _connectivityResult + // .add(networkInformationToConnectivityResult(_networkInformation)); + // }); + // Once we can detect when to *cancel* a subscription to the _networkInformation + // onChange Stream upon hot restart. + // https://github.com/dart-lang/sdk/issues/42679 + _connectivityResultStream = + _connectivityResultStreamController.stream.asBroadcastStream(); + } + return _connectivityResultStream; + } +} diff --git a/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart b/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart new file mode 100644 index 000000000000..efefd8d52440 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/lib/src/utils/connectivity_result.dart @@ -0,0 +1,51 @@ +import 'dart:html' as html show NetworkInformation; +import 'package:connectivity_platform_interface/connectivity_platform_interface.dart'; + +/// Converts an incoming NetworkInformation object into the correct ConnectivityResult. +ConnectivityResult networkInformationToConnectivityResult( + html.NetworkInformation info, +) { + if (info == null) { + return ConnectivityResult.none; + } + if (info.downlink == 0 && info.rtt == 0) { + return ConnectivityResult.none; + } + if (info.effectiveType != null) { + return _effectiveTypeToConnectivityResult(info.effectiveType); + } + if (info.type != null) { + return _typeToConnectivityResult(info.type); + } + return ConnectivityResult.none; +} + +ConnectivityResult _effectiveTypeToConnectivityResult(String effectiveType) { + // Possible values: + /*'2g'|'3g'|'4g'|'slow-2g'*/ + switch (effectiveType) { + case 'slow-2g': + case '2g': + case '3g': + return ConnectivityResult.mobile; + default: + return ConnectivityResult.wifi; + } +} + +ConnectivityResult _typeToConnectivityResult(String type) { + // Possible values: + /*'bluetooth'|'cellular'|'ethernet'|'mixed'|'none'|'other'|'unknown'|'wifi'|'wimax'*/ + switch (type) { + case 'none': + return ConnectivityResult.none; + case 'bluetooth': + case 'cellular': + case 'mixed': + case 'other': + case 'unknown': + return ConnectivityResult.mobile; + default: + return ConnectivityResult.wifi; + } +} diff --git a/packages/connectivity/connectivity_for_web/pubspec.yaml b/packages/connectivity/connectivity_for_web/pubspec.yaml new file mode 100644 index 000000000000..e1142a75c91f --- /dev/null +++ b/packages/connectivity/connectivity_for_web/pubspec.yaml @@ -0,0 +1,31 @@ +name: connectivity_for_web +description: An implementation for the web platform of the Flutter `connectivity` plugin. This uses the NetworkInformation Web API, with a fallback to Navigator.onLine. +version: 0.3.1 +homepage: https://github.com/ditman/plugins/tree/connectivity-web/packages/connectivity/experimental_connectivity_web + +flutter: + plugin: + platforms: + web: + pluginClass: ConnectivityPlugin + fileName: connectivity_for_web.dart + +dependencies: + connectivity_platform_interface: ^1.0.3 + flutter_web_plugins: + sdk: flutter + flutter: + sdk: flutter + +dev_dependencies: + test: any + flutter_driver: + sdk: flutter + flutter_test: + sdk: flutter + e2e: ^0.2.4+3 + mockito: ^4.1.1 + +environment: + sdk: ">=2.6.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/connectivity/connectivity_for_web/test/.gitignore b/packages/connectivity/connectivity_for_web/test/.gitignore new file mode 100644 index 000000000000..d7dee828a6b9 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/test/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ +lib/generated_plugin_registrant.dart diff --git a/packages/connectivity/connectivity_for_web/test/lib/main.dart b/packages/connectivity/connectivity_for_web/test/lib/main.dart new file mode 100644 index 000000000000..93b9a73cf31b --- /dev/null +++ b/packages/connectivity/connectivity_for_web/test/lib/main.dart @@ -0,0 +1,77 @@ +import 'package:e2e/e2e.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:connectivity_platform_interface/connectivity_platform_interface.dart'; +import 'package:connectivity_for_web/src/network_information_api_connectivity_plugin.dart'; + +import 'package:mockito/mockito.dart'; + +import 'src/connectivity_mocks.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + group('checkConnectivity', () { + void testCheckConnectivity({ + String type, + String effectiveType, + num downlink = 10, + num rtt = 50, + ConnectivityResult expected, + }) { + final connection = MockNetworkInformation(); + when(connection.type).thenReturn(type); + when(connection.effectiveType).thenReturn(effectiveType); + when(connection.downlink).thenReturn(downlink); + when(connection.rtt).thenReturn(downlink); + + NetworkInformationApiConnectivityPlugin plugin = + NetworkInformationApiConnectivityPlugin.withConnection(connection); + expect(plugin.checkConnectivity(), completion(equals(expected))); + } + + test('0 downlink and rtt -> none', () { + testCheckConnectivity( + effectiveType: '4g', + downlink: 0, + rtt: 0, + expected: ConnectivityResult.none); + }); + test('slow-2g -> mobile', () { + testCheckConnectivity( + effectiveType: 'slow-2g', expected: ConnectivityResult.mobile); + }); + test('2g -> mobile', () { + testCheckConnectivity( + effectiveType: '2g', expected: ConnectivityResult.mobile); + }); + test('3g -> mobile', () { + testCheckConnectivity( + effectiveType: '3g', expected: ConnectivityResult.mobile); + }); + test('4g -> wifi', () { + testCheckConnectivity( + effectiveType: '4g', expected: ConnectivityResult.wifi); + }); + }); + + group('get onConnectivityChanged', () { + test('puts change events in a Stream', () async { + final connection = MockNetworkInformation(); + NetworkInformationApiConnectivityPlugin plugin = + NetworkInformationApiConnectivityPlugin.withConnection(connection); + + Stream results = plugin.onConnectivityChanged; + + // Fake a disconnect-reconnect + await connection.mockChangeValue(downlink: 0, rtt: 0); + await connection.mockChangeValue( + downlink: 10, rtt: 50, effectiveType: '4g'); + + // The stream of results is infinite, so we need to .take(2) for this test to complete. + expect( + results.take(2).toList(), + completion( + equals([ConnectivityResult.none, ConnectivityResult.wifi]))); + }); + }); +} diff --git a/packages/connectivity/connectivity_for_web/test/lib/src/connectivity_mocks.dart b/packages/connectivity/connectivity_for_web/test/lib/src/connectivity_mocks.dart new file mode 100644 index 000000000000..7b82b512065b --- /dev/null +++ b/packages/connectivity/connectivity_for_web/test/lib/src/connectivity_mocks.dart @@ -0,0 +1,25 @@ +import 'dart:html'; + +import 'package:mockito/mockito.dart'; + +/// A Mock implementation of the NetworkInformation API that allows +/// for external modification of its values. +class MockNetworkInformation extends Mock implements NetworkInformation { + /// The callback that will fire after the network information values change. + Function onchange; + + /// Changes the desired values, and triggers the change event listener. + void mockChangeValue({ + String type, + String effectiveType, + num downlink, + num rtt, + }) async { + when(this.type).thenAnswer((_) => type); + when(this.effectiveType).thenAnswer((_) => effectiveType); + when(this.downlink).thenAnswer((_) => downlink); + when(this.rtt).thenAnswer((_) => rtt); + + onchange(Event('change')); + } +} diff --git a/packages/connectivity/connectivity_for_web/test/pubspec.yaml b/packages/connectivity/connectivity_for_web/test/pubspec.yaml new file mode 100644 index 000000000000..44f4b552b443 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/test/pubspec.yaml @@ -0,0 +1,25 @@ +name: connectivity_web_example +description: Example web app for the connectivity plugin +version: 0.1.1 +homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity/connectivity_web + +dependencies: + connectivity_for_web: + path: ../ + js: ^0.6.1+1 + flutter_web_plugins: + sdk: flutter + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_driver: + sdk: flutter + e2e: ^0.2.4+3 + mockito: ^4.1.1 + +environment: + sdk: ">=2.6.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4" diff --git a/packages/connectivity/connectivity_for_web/test/web/index.html b/packages/connectivity/connectivity_for_web/test/web/index.html new file mode 100644 index 000000000000..6eff9a740d43 --- /dev/null +++ b/packages/connectivity/connectivity_for_web/test/web/index.html @@ -0,0 +1,10 @@ + + + + + example + + + + + diff --git a/packages/e2e/CHANGELOG.md b/packages/e2e/CHANGELOG.md index 725b648dc5a5..255d7ce08c2d 100644 --- a/packages/e2e/CHANGELOG.md +++ b/packages/e2e/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.6.1 + +* Added `data` in the reported json. + +## 0.6.0 + +* **Breaking change** `E2EPlugin` exports a `Future` for `testResults`. + +## 0.5.0+1 + +* Fixed the device pixel ratio problem. + ## 0.5.0 * **Breaking change** by default, tests will use the device window size. diff --git a/packages/e2e/android/build.gradle b/packages/e2e/android/build.gradle index ec686f4b4e3d..d0bb6c5a5967 100644 --- a/packages/e2e/android/build.gradle +++ b/packages/e2e/android/build.gradle @@ -33,11 +33,12 @@ android { } dependencies { api 'junit:junit:4.12' - implementation 'net.sourceforge.streamsupport:android-retrofuture:1.7.2' // https://developer.android.com/jetpack/androidx/releases/test/#1.2.0 api 'androidx.test:runner:1.2.0' api 'androidx.test:rules:1.2.0' api 'androidx.test.espresso:espresso-core:3.2.0' + + implementation 'com.google.guava:guava:28.1-android' } } diff --git a/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java b/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java index f6c5b306b5d9..31100d442731 100644 --- a/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java +++ b/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java @@ -5,6 +5,7 @@ package dev.flutter.plugins.e2e; import android.content.Context; +import com.google.common.util.concurrent.SettableFuture; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; @@ -13,13 +14,15 @@ import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; import java.util.Map; -import java9.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; /** E2EPlugin */ public class E2EPlugin implements MethodCallHandler, FlutterPlugin { private MethodChannel methodChannel; - public static CompletableFuture> testResults = new CompletableFuture<>(); + private static final SettableFuture> testResultsSettable = + SettableFuture.create(); + public static final Future> testResults = testResultsSettable; private static final String CHANNEL = "plugins.flutter.io/e2e"; @@ -49,7 +52,7 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) { public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("allTestsFinished")) { Map results = call.argument("results"); - testResults.complete(results); + testResultsSettable.set(results); result.success(null); } else { result.notImplemented(); diff --git a/packages/e2e/lib/common.dart b/packages/e2e/lib/common.dart index 39efcc867704..4325b6555ba8 100644 --- a/packages/e2e/lib/common.dart +++ b/packages/e2e/lib/common.dart @@ -11,13 +11,17 @@ class Response { final bool _allTestsPassed; + /// The extra information to be added along side the test result. + Map data; + /// Constructor to use for positive response. - Response.allTestsPassed() + Response.allTestsPassed({this.data}) : this._allTestsPassed = true, this._failureDetails = null; /// Constructor for failure response. - Response.someTestsFailed(this._failureDetails) : this._allTestsPassed = false; + Response.someTestsFailed(this._failureDetails, {this.data}) + : this._allTestsPassed = false; /// Whether the test ran successfully or not. bool get allTestsPassed => _allTestsPassed; @@ -33,16 +37,19 @@ class Response { String toJson() => json.encode({ 'result': allTestsPassed.toString(), 'failureDetails': _failureDetailsAsString(), + if (data != null) 'data': data }); /// Deserializes the result from JSON. static Response fromJson(String source) { - Map responseJson = json.decode(source); - if (responseJson['result'] == 'true') { - return Response.allTestsPassed(); + final Map responseJson = json.decode(source); + if (responseJson['result'] as String == 'true') { + return Response.allTestsPassed(data: responseJson['data']); } else { return Response.someTestsFailed( - _failureDetailsFromJson(responseJson['failureDetails'])); + _failureDetailsFromJson(responseJson['failureDetails']), + data: responseJson['data'], + ); } } diff --git a/packages/e2e/lib/e2e.dart b/packages/e2e/lib/e2e.dart index c2ddb2d6b801..a9bf83041e40 100644 --- a/packages/e2e/lib/e2e.dart +++ b/packages/e2e/lib/e2e.dart @@ -52,8 +52,8 @@ class E2EWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding { bool get registerTestTextInput => false; @override - ViewConfiguration createViewConfiguration() => - TestViewConfiguration(size: window.physicalSize); + ViewConfiguration createViewConfiguration() => TestViewConfiguration( + size: window.physicalSize / window.devicePixelRatio); final Completer _allTestsPassed = Completer(); @@ -78,33 +78,47 @@ class E2EWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding { static Map _results = {}; + /// The extra data for the reported result. + /// + /// The values in `reportData` must be json-serializable objects or `null`. + /// If it's `null`, no extra data is attached to the result. + /// + /// The default value is `null`. + Map reportData; + + /// the callback function to response the driver side input. + @visibleForTesting + Future> callback(Map params) async { + final String command = params['command']; + Map response; + switch (command) { + case 'request_data': + final bool allTestsPassed = await _allTestsPassed.future; + response = { + 'message': allTestsPassed + ? Response.allTestsPassed(data: reportData).toJson() + : Response.someTestsFailed( + _failureMethodsDetails, + data: reportData, + ).toJson(), + }; + break; + case 'get_health': + response = {'status': 'ok'}; + break; + default: + throw UnimplementedError('$command is not implemented'); + } + return { + 'isError': false, + 'response': response, + }; + } + // Emulates the Flutter driver extension, returning 'pass' or 'fail'. @override void initServiceExtensions() { super.initServiceExtensions(); - Future> callback(Map params) async { - final String command = params['command']; - Map response; - switch (command) { - case 'request_data': - final bool allTestsPassed = await _allTestsPassed.future; - response = { - 'message': allTestsPassed - ? Response.allTestsPassed().toJson() - : Response.someTestsFailed(_failureMethodsDetails).toJson(), - }; - break; - case 'get_health': - response = {'status': 'ok'}; - break; - default: - throw UnimplementedError('$command is not implemented'); - } - return { - 'isError': false, - 'response': response, - }; - } if (kIsWeb) { registerWebServiceExtension(callback); diff --git a/packages/e2e/pubspec.yaml b/packages/e2e/pubspec.yaml index 82de42bd5c66..70e57d0cb4f7 100644 --- a/packages/e2e/pubspec.yaml +++ b/packages/e2e/pubspec.yaml @@ -1,10 +1,10 @@ name: e2e description: Runs tests that use the flutter_test API as integration tests. -version: 0.5.0 +version: 0.6.1 homepage: https://github.com/flutter/plugins/tree/master/packages/e2e environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.2.2 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" dependencies: diff --git a/packages/e2e/test/binding_test.dart b/packages/e2e/test/binding_test.dart new file mode 100644 index 000000000000..8c97e161b0fe --- /dev/null +++ b/packages/e2e/test/binding_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +import 'package:e2e/e2e.dart'; +import 'package:e2e/common.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + Future> request; + + group('Test E2E binding', () { + final WidgetsBinding binding = E2EWidgetsFlutterBinding.ensureInitialized(); + assert(binding is E2EWidgetsFlutterBinding); + final E2EWidgetsFlutterBinding e2ebinding = + binding as E2EWidgetsFlutterBinding; + + setUp(() { + request = e2ebinding.callback({ + 'command': 'request_data', + }); + }); + + testWidgets('Run E2E app', (WidgetTester tester) async { + runApp(MaterialApp( + home: Text('Test'), + )); + expect(tester.binding, e2ebinding); + e2ebinding.reportData = {'answer': 42}; + }); + }); + + tearDownAll(() async { + // This part is outside the group so that `request` has been compeleted as + // part of the `tearDownAll` registerred in the group during + // `E2EWidgetsFlutterBinding` initialization. + final Map response = + (await request)['response'] as Map; + final String message = response['message'] as String; + Response result = Response.fromJson(message); + assert(result.data['answer'] == 42); + }); +} diff --git a/packages/e2e/test/response_serialization_test.dart b/packages/e2e/test/response_serialization_test.dart new file mode 100644 index 000000000000..4d823e2ebf10 --- /dev/null +++ b/packages/e2e/test/response_serialization_test.dart @@ -0,0 +1,47 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:e2e/common.dart'; + +void main() { + test('Serialize and deserialize Failure', () { + Failure fail = Failure('what a name', 'no detail'); + Failure restored = Failure.fromJsonString(fail.toString()); + expect(restored.methodName, fail.methodName); + expect(restored.details, fail.details); + }); + + test('Serialize and deserialize Response', () { + Response response, restored; + String jsonString; + + response = Response.allTestsPassed(); + jsonString = response.toJson(); + expect(jsonString, '{"result":"true","failureDetails":[]}'); + restored = Response.fromJson(jsonString); + expect(restored.allTestsPassed, response.allTestsPassed); + expect(restored.data, null); + expect(restored.formattedFailureDetails, ''); + + final Failure fail = Failure('what a name', 'no detail'); + final Failure fail2 = Failure('what a name2', 'no detail2'); + response = Response.someTestsFailed([fail, fail2]); + jsonString = response.toJson(); + restored = Response.fromJson(jsonString); + expect(restored.allTestsPassed, response.allTestsPassed); + expect(restored.data, null); + expect(restored.formattedFailureDetails, response.formattedFailureDetails); + + Map data = {'aaa': 'bbb'}; + response = Response.allTestsPassed(data: data); + jsonString = response.toJson(); + restored = Response.fromJson(jsonString); + expect(restored.data.keys, ['aaa']); + expect(restored.data.values, ['bbb']); + + response = Response.someTestsFailed([fail, fail2], data: data); + jsonString = response.toJson(); + restored = Response.fromJson(jsonString); + expect(restored.data.keys, ['aaa']); + expect(restored.data.values, ['bbb']); + }); +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/.gitignore b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/.gitignore new file mode 100644 index 000000000000..bb431f0d5b47 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/.gitignore @@ -0,0 +1,75 @@ +# 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/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/.metadata b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/.metadata new file mode 100644 index 000000000000..2c91cc0fc35a --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 5e3e5a2a1a977c34b22f3709109fd237b5cab9c6 + channel: master + +project_type: package diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md new file mode 100644 index 000000000000..7f35c8490e1f --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* First published version. diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/LICENSE b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/LICENSE new file mode 100644 index 000000000000..14fbc7c48e1c --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/LICENSE @@ -0,0 +1,27 @@ +Copyright 2020 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 LLC 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/google_sign_in/extension_google_sign_in_as_googleapis_auth/README.md b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/README.md new file mode 100644 index 000000000000..78ddb4bdc91b --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/README.md @@ -0,0 +1,46 @@ +# extension_google_sign_in_as_googleapis_auth + +A bridge package between Flutter's [`google_sign_in` plugin](https://pub.dev/packages/google_sign_in) and Dart's [`googleapis` package](https://pub.dev/packages/googleapis), that is able to create [`googleapis_auth`-like `AuthClient` instances](https://pub.dev/documentation/googleapis_auth/latest/googleapis_auth.auth/AuthClient-class.html) directly from the `GoogleSignIn` plugin. + +## Usage + +This package is implemented as an [extension method](https://dart.dev/guides/language/extension-methods) on top of the `GoogleSignIn` plugin. + +In order to use it, you need to add a `dependency` to your `pubspec.yaml`. Then, wherever you're importing `package:google_sign_in/google_sign_in.dart`, add the following: + +```dart +... +import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart'; +... +``` + +From that moment on, your `GoogleSignIn` instance will have an additional `Future authenticatedClient()` method that you can call once your sign in is successful to retrieve an `AuthClient`. + +That object can then be used to create instances of `googleapis` API clients: + +```dart +... +final peopleApi = PeopleApi(await _googleSignIn.authenticatedClient()); +final response = await peopleApi.people.connections.list( + 'people/me', + personFields: 'names', +); +... +``` + +## Example + +This package contains a modified version of Flutter's Google Sign In example app that uses `package:googleapis`' API clients, instead of raw http requests. + +See it [here](https://github.com/flutter/plugins/blob/master/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/lib/main.dart). + +The original code (and its license) can be seen [here](https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in/example/lib/main.dart). + +## Testing + +Run tests with `flutter test`. + +## Issues and feedback + +Please file [issues](https://github.com/flutter/flutter/issues/new) +to send feedback or report a bug. Thank you! diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md new file mode 100755 index 000000000000..689071dcfe8d --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/README.md @@ -0,0 +1,8 @@ +# extension_google_sign_in_example + +Demonstrates how to use the google_sign_in plugin with the `googleapis` package. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/build.gradle b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/build.gradle new file mode 100755 index 000000000000..e6da1a0aebf5 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/build.gradle @@ -0,0 +1,64 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "io.flutter.plugins.googlesigninexample" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } + + testOptions { + unitTests.returnDefaultValues = true + } +} + +flutter { + source '../..' +} + +dependencies { + implementation 'com.google.android.gms:play-services-auth:16.0.1' + testImplementation'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:2.17.0' +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/google-services.json b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/google-services.json new file mode 100644 index 000000000000..efa524535553 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/google-services.json @@ -0,0 +1,246 @@ +{ + "project_info": { + "project_number": "479882132969", + "firebase_url": "https://my-flutter-proj.firebaseio.com", + "project_id": "my-flutter-proj", + "storage_bucket": "my-flutter-proj.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:479882132969:android:c73fd19ff7e2c0be", + "android_client_info": { + "package_name": "io.flutter.plugins.cameraexample" + } + }, + "oauth_client": [ + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:479882132969:android:632cdf3fc0a17139", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasedynamiclinksexample" + } + }, + "oauth_client": [ + { + "client_id": "479882132969-32qusitiag53931ck80h121ajhlc5a7e.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebasedynamiclinksexample", + "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" + } + }, + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" + } + } + ] + }, + "ads_service": { + "status": 2 + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:479882132969:android:ae50362b4bc06086", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasemlvisionexample" + } + }, + "oauth_client": [ + { + "client_id": "479882132969-9pp74fkgmtvt47t9rikc1p861v7n85tn.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebasemlvisionexample", + "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" + } + }, + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" + } + } + ] + }, + "ads_service": { + "status": 2 + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:479882132969:android:215a22700e1b466b", + "android_client_info": { + "package_name": "io.flutter.plugins.firebaseperformanceexample" + } + }, + "oauth_client": [ + { + "client_id": "479882132969-8h4kiv8m7ho4tvn6uuujsfcrf69unuf7.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseperformanceexample", + "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" + } + }, + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" + } + } + ] + }, + "ads_service": { + "status": 2 + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:479882132969:android:5e9f1f89e134dc86", + "android_client_info": { + "package_name": "io.flutter.plugins.googlesigninexample" + } + }, + "oauth_client": [ + { + "client_id": "479882132969-90ml692hkonp587sl0v0rurmnvkekgrg.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.googlesigninexample", + "certificate_hash": "e733b7a303250b63e06de6f7c9767c517d69cfa0" + } + }, + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCrZz9T0Pg0rDnpxfNuPBrOxGhXskfebXs" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 2, + "other_platform_oauth_client": [ + { + "client_id": "479882132969-0d20fkjtr1p8evfomfkf3vmi50uajml2.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "479882132969-gjp4e63ogu2h6guttj2ie6t3f10ic7i8.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "io.flutter.plugins.firebaseMlVisionExample" + } + } + ] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..9a4163a4f5ee --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/AndroidManifest.xml b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/AndroidManifest.xml new file mode 100755 index 000000000000..df80f829c1e7 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/.gitignore b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/.gitignore new file mode 100755 index 000000000000..9eb4563d2ae1 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/.gitignore @@ -0,0 +1 @@ +GeneratedPluginRegistrant.java diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1Activity.java b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..f7ea0c4043a6 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1Activity.java @@ -0,0 +1,19 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlesigninexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.googlesignin.GoogleSignInPlugin; +import io.flutter.view.FlutterMain; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + FlutterMain.startInitialization(this); + super.onCreate(savedInstanceState); + GoogleSignInPlugin.registerWith(registrarFor("io.flutter.plugins.googlesignin")); + } +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1ActivityTest.java b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..8bddbff7ce27 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,17 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlesigninexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java new file mode 100644 index 000000000000..77cdcee9bcdb --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/java/io/flutter/plugins/googlesigninexample/FlutterActivityTest.java @@ -0,0 +1,17 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.googlesigninexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import io.flutter.embedding.android.FlutterActivity; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class FlutterActivityTest { + @Rule + public ActivityTestRule rule = new ActivityTestRule<>(FlutterActivity.class); +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100755 index 000000000000..db77bb4b7b09 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100755 index 000000000000..17987b79bb8a Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100755 index 000000000000..09d4391482be Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100755 index 000000000000..d5f1c8d34e7a Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100755 index 000000000000..4d6372eebdb2 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/values/strings.xml b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/values/strings.xml new file mode 100644 index 000000000000..c7e28ffcedd1 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + YOUR_WEB_CLIENT_ID + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle new file mode 100755 index 000000000000..541636cc492a --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.3.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/gradle.properties b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/gradle.properties new file mode 100755 index 000000000000..38c8d4544ff1 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..019065d1d650 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle new file mode 100755 index 000000000000..115da6cb4f4d --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withInputStream { stream -> plugins.load(stream) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/AppFrameworkInfo.plist b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100755 index 000000000000..6c2de8086bcd --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,30 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + UIRequiredDeviceCapabilities + + arm64 + + MinimumOSVersion + 8.0 + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/Debug.xcconfig b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/Debug.xcconfig new file mode 100755 index 000000000000..9803018ca79d --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/Release.xcconfig b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/Release.xcconfig new file mode 100755 index 000000000000..a4a8c604e13d --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Generated.xcconfig" +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/GoogleSignInPluginTest/Info.plist b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/GoogleSignInPluginTest/Info.plist new file mode 100644 index 000000000000..64d65ca49577 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/GoogleSignInPluginTest/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..faaaa58070bd --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,502 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */; }; + 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */; }; + 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F582639B44581540871D9BB0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C2FB9CBA01DB0A2DE5F31E12 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + isa = PBXGroup; + children = ( + 5A76713E622F06379AEDEBFA /* Pods-Runner.debug.xcconfig */, + F582639B44581540871D9BB0 /* Pods-Runner.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 7ACDFB0D1E8944C400BE2D00 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 840012C8B5EDBCF56B0E4AC1 /* Pods */, + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 5C6F5A6C1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.h */, + 5C6F5A6D1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m */, + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 7A303C2D1E89D76400B1F19E /* GoogleService-Info.plist */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0263E28FA425D1CE928BDE15 /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */, + 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1100; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7A303C2E1E89D76400B1F19E /* GoogleService-Info.plist in Resources */, + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 7ACDFB0E1E8944C400BE2D00 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed\n/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin\n"; + }; + 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${PODS_ROOT}/../Flutter/Flutter.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 5C6F5A6E1EC3B4CB008D64B5 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.googleSignInExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.googleSignInExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100755 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100755 index 000000000000..3bb3697ef41c --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100755 index 000000000000..21a3cc14c74e --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/AppDelegate.h b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/AppDelegate.h new file mode 100644 index 000000000000..d9e18e990f2e --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/AppDelegate.h @@ -0,0 +1,10 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/AppDelegate.m b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/AppDelegate.m new file mode 100644 index 000000000000..f08675707182 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/AppDelegate.m @@ -0,0 +1,17 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100755 index 000000000000..d22f10b2ab63 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100755 index 000000000000..28c6bf03016f Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100755 index 000000000000..2ccbfd967d96 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100755 index 000000000000..f091b6b0bca8 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100755 index 000000000000..4cde12118dda Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100755 index 000000000000..d0ef06e7edb8 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100755 index 000000000000..dcdc2306c285 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100755 index 000000000000..2ccbfd967d96 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100755 index 000000000000..c8f9ed8f5cee Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100755 index 000000000000..a6d6b8609df0 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100755 index 000000000000..a6d6b8609df0 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100755 index 000000000000..75b2d164a5a9 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100755 index 000000000000..c4df70d39da7 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100755 index 000000000000..6a84f41e14e2 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100755 index 000000000000..d0e1f5853602 Binary files /dev/null and b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100755 index 000000000000..ebf48f603974 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Base.lproj/Main.storyboard b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100755 index 000000000000..f3c28516fb38 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/GoogleService-Info.plist b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/GoogleService-Info.plist new file mode 100644 index 000000000000..6042aab908af --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,44 @@ + + + + + AD_UNIT_ID_FOR_BANNER_TEST + ca-app-pub-3940256099942544/2934735716 + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + ca-app-pub-3940256099942544/4411468910 + CLIENT_ID + 479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u + ANDROID_CLIENT_ID + 479882132969-jie8r1me6dsra60pal6ejaj8dgme3tg0.apps.googleusercontent.com + API_KEY + AIzaSyBECOwLTAN6PU4Aet1b2QLGIb3kRK8Xjew + GCM_SENDER_ID + 479882132969 + PLIST_VERSION + 1 + BUNDLE_ID + io.flutter.plugins.googleSignInExample + PROJECT_ID + my-flutter-proj + STORAGE_BUCKET + my-flutter-proj.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:479882132969:ios:2643f950e0a0da08 + DATABASE_URL + https://my-flutter-proj.firebaseio.com + SERVER_CLIENT_ID + YOUR_SERVER_CLIENT_ID + + \ No newline at end of file diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist new file mode 100755 index 000000000000..e03ccfe55e37 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/Info.plist @@ -0,0 +1,62 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Google Sign-In Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + GoogleSignInExample + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/main.m b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/main.m new file mode 100644 index 000000000000..bec320c0bee0 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/ios/Runner/main.m @@ -0,0 +1,13 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/lib/main.dart b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/lib/main.dart new file mode 100755 index 000000000000..a238ca3bf8b5 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/lib/main.dart @@ -0,0 +1,149 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; + +import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart'; +import 'package:googleapis/people/v1.dart'; + +GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'https://www.googleapis.com/auth/contacts.readonly', + ], +); + +void main() { + runApp( + MaterialApp( + title: 'Google Sign In', + home: SignInDemo(), + ), + ); +} + +class SignInDemo extends StatefulWidget { + @override + State createState() => SignInDemoState(); +} + +class SignInDemoState extends State { + GoogleSignInAccount _currentUser; + String _contactText; + + @override + void initState() { + super.initState(); + _googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) { + setState(() { + _currentUser = account; + }); + if (_currentUser != null) { + _handleGetContact(); + } + }); + _googleSignIn.signInSilently(); + } + + Future _handleGetContact() async { + setState(() { + _contactText = 'Loading contact info...'; + }); + + final peopleApi = PeopleApi(await _googleSignIn.authenticatedClient()); + final response = await peopleApi.people.connections.list( + 'people/me', + personFields: 'names', + ); + + final firstNamedContactName = _pickFirstNamedContact(response.connections); + + setState(() { + if (firstNamedContactName != null) { + _contactText = 'I see you know $firstNamedContactName!'; + } else { + _contactText = 'No contacts to display.'; + } + }); + } + + String _pickFirstNamedContact(List connections) { + return connections + ?.firstWhere( + (person) => person.names != null, + orElse: () => null, + ) + ?.names + ?.firstWhere( + (name) => name.displayName != null, + orElse: () => null, + ) + ?.displayName; + } + + Future _handleSignIn() async { + try { + await _googleSignIn.signIn(); + } catch (error) { + print(error); + } + } + + Future _handleSignOut() => _googleSignIn.disconnect(); + + Widget _buildBody() { + if (_currentUser != null) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + ListTile( + leading: GoogleUserCircleAvatar( + identity: _currentUser, + ), + title: Text(_currentUser.displayName ?? ''), + subtitle: Text(_currentUser.email ?? ''), + ), + const Text('Signed in successfully.'), + Text(_contactText ?? ''), + RaisedButton( + child: const Text('SIGN OUT'), + onPressed: _handleSignOut, + ), + RaisedButton( + child: const Text('REFRESH'), + onPressed: _handleGetContact, + ), + ], + ); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const Text('You are not currently signed in.'), + RaisedButton( + child: const Text('SIGN IN'), + onPressed: _handleSignIn, + ), + ], + ); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Google Sign In'), + ), + body: ConstrainedBox( + constraints: const BoxConstraints.expand(), + child: _buildBody(), + )); + } +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml new file mode 100755 index 000000000000..aee073971d2c --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/pubspec.yaml @@ -0,0 +1,23 @@ +name: extension_google_sign_in_example +description: Example of Google Sign-In plugin and googleapis. + +dependencies: + flutter: + sdk: flutter + google_sign_in: ^4.4.1 + extension_google_sign_in_as_googleapis_auth: + path: ../ + googleapis: ^0.55.0 + +dev_dependencies: + pedantic: ^1.8.0 + e2e: ^0.2.1 + flutter_driver: + sdk: flutter + +flutter: + uses-material-design: true + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4 <2.0.0" diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/web/index.html b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/web/index.html new file mode 100644 index 000000000000..42a7d93582ba --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/example/web/index.html @@ -0,0 +1,11 @@ + + + + + + Google Sign-in Example + + + + + diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/lib/extension_google_sign_in_as_googleapis_auth.dart b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/lib/extension_google_sign_in_as_googleapis_auth.dart new file mode 100644 index 000000000000..eec45cc0e89a --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/lib/extension_google_sign_in_as_googleapis_auth.dart @@ -0,0 +1,36 @@ +// Copyright 2020 The Flutter Authors +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +import 'package:meta/meta.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:googleapis_auth/auth.dart' as googleapis_auth; +import 'package:http/http.dart' as http; + +/// Extension on [GoogleSignIn] that adds an `authenticatedClient` method. +/// +/// This method can be used to retrieve an authenticated [googleapis_auth.AuthClient] +/// client that can be used with the rest of the `googleapis` libraries. +extension GoogleApisGoogleSignInAuth on GoogleSignIn { + /// Retrieve a `googleapis` authenticated client. + Future authenticatedClient({ + @visibleForTesting GoogleSignInAuthentication debugAuthentication, + @visibleForTesting List debugScopes = const [], + }) async { + final auth = debugAuthentication ?? await currentUser.authentication; + final credentials = googleapis_auth.AccessCredentials( + googleapis_auth.AccessToken( + 'Bearer', + auth.accessToken, + // We don't know when the token expires, so we assume "never" + DateTime.now().toUtc().add(Duration(days: 365)), + ), + null, // We don't have a refreshToken + debugScopes ?? this.scopes, + ); + + return googleapis_auth.authenticatedClient(http.Client(), credentials); + } +} diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml new file mode 100644 index 000000000000..f94b5e505946 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/pubspec.yaml @@ -0,0 +1,28 @@ +# Copyright 2020 The Flutter Authors +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +name: extension_google_sign_in_as_googleapis_auth +description: A bridge package between google_sign_in and googleapis_auth, to create Authenticated Clients from google_sign_in user credentials. +version: 1.0.0 +homepage: https://github.com/flutter/plugins/google_sign_in/extension_google_sign_in_as_googleapis_auth + +dependencies: + flutter: + sdk: flutter + google_sign_in: ^4.4.1 + googleapis_auth: ^0.2.11+1 + meta: ^1.1.8 + http: ^0.12.1 + +dev_dependencies: + mockito: ^4.1.1 + pedantic: ^1.9.0 + flutter_test: + sdk: flutter + +environment: + sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.12.13+hotfix.4 <2.0.0" diff --git a/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/test/extension_google_sign_in_as_googleapis_auth_test.dart b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/test/extension_google_sign_in_as_googleapis_auth_test.dart new file mode 100644 index 000000000000..9f86703d4bb8 --- /dev/null +++ b/packages/google_sign_in/extension_google_sign_in_as_googleapis_auth/test/extension_google_sign_in_as_googleapis_auth_test.dart @@ -0,0 +1,46 @@ +// Copyright 2020 The Flutter Authors +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:googleapis_auth/auth.dart' as auth; +import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart'; +import 'package:mockito/mockito.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// Mocks so I don't have to prepare all the GoogleSignIn environment. +class MockGoogleSignIn extends Mock implements GoogleSignIn {} + +class MockGoogleSignInAuthentication extends Mock + implements GoogleSignInAuthentication {} + +const SOME_FAKE_ACCESS_TOKEN = 'this-is-something-not-null'; +const SOME_FAKE_SCOPES = ['some-scope', 'another-scope']; + +void main() { + GoogleSignIn signIn = MockGoogleSignIn(); + final authMock = MockGoogleSignInAuthentication(); + + setUp(() { + when(authMock.accessToken).thenReturn(SOME_FAKE_ACCESS_TOKEN); + }); + + test('authenticatedClient returns an authenticated client', () async { + final client = await signIn.authenticatedClient( + debugAuthentication: authMock, + ); + expect(client, isA()); + }); + + test('authenticatedClient returned client contains the passed-in credentials', + () async { + final client = await signIn.authenticatedClient( + debugAuthentication: authMock, + debugScopes: SOME_FAKE_SCOPES, + ); + expect(client.credentials.accessToken.data, equals(SOME_FAKE_ACCESS_TOKEN)); + expect(client.credentials.scopes, equals(SOME_FAKE_SCOPES)); + }); +} diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md index a72e3bcbc221..0cec3a32b5f1 100644 --- a/packages/image_picker/image_picker/CHANGELOG.md +++ b/packages/image_picker/image_picker/CHANGELOG.md @@ -1,3 +1,21 @@ +## 0.6.7+4 + +* Support iOS simulator x86_64 architecture. + +## 0.6.7+3 + +* Fixes to the example app: + * Make videos in web start muted. This allows auto-play across browsers. + * Prevent the app from disposing of video controllers too early. + +## 0.6.7+2 + +* iOS: Fixes unpresentable album/image picker if window's root view controller is already presenting other view controller. + +## 0.6.7+1 + +* Add web support to the example app. + ## 0.6.7 * Utilize the new platform_interface package. diff --git a/packages/image_picker/image_picker/example/lib/main.dart b/packages/image_picker/image_picker/example/lib/main.dart index 231dcb25931b..ece8c45d9c8e 100755 --- a/packages/image_picker/image_picker/example/lib/main.dart +++ b/packages/image_picker/image_picker/example/lib/main.dart @@ -42,6 +42,7 @@ class _MyHomePageState extends State { dynamic _pickImageError; bool isVideo = false; VideoPlayerController _controller; + VideoPlayerController _toBeDisposed; String _retrieveDataError; final ImagePicker _picker = ImagePicker(); @@ -52,8 +53,18 @@ class _MyHomePageState extends State { Future _playVideo(PickedFile file) async { if (file != null && mounted) { await _disposeVideoController(); - _controller = VideoPlayerController.file(File(file.path)); - await _controller.setVolume(1.0); + if (kIsWeb) { + _controller = VideoPlayerController.network(file.path); + // In web, most browsers won't honor a programmatic call to .play + // if the video has a sound track (and is not muted). + // Mute the video so it auto-plays in web! + // This is not needed if the call to .play is the result of user + // interaction (clicking on a "play" button, for example). + await _controller.setVolume(0.0); + } else { + _controller = VideoPlayerController.file(File(file.path)); + await _controller.setVolume(1.0); + } await _controller.initialize(); await _controller.setLooping(true); await _controller.play(); @@ -110,10 +121,11 @@ class _MyHomePageState extends State { } Future _disposeVideoController() async { - if (_controller != null) { - await _controller.dispose(); - _controller = null; + if (_toBeDisposed != null) { + await _toBeDisposed.dispose(); } + _toBeDisposed = _controller; + _controller = null; } Widget _previewVideo() { @@ -139,7 +151,13 @@ class _MyHomePageState extends State { return retrieveError; } if (_imageFile != null) { - return Image.file(File(_imageFile.path)); + if (kIsWeb) { + // Why network? + // See https://pub.dev/packages/image_picker#getting-ready-for-the-web-platform + return Image.network(_imageFile.path); + } else { + return Image.file(File(_imageFile.path)); + } } else if (_pickImageError != null) { return Text( 'Pick image error: $_pickImageError', @@ -180,7 +198,7 @@ class _MyHomePageState extends State { title: Text(widget.title), ), body: Center( - child: defaultTargetPlatform == TargetPlatform.android + child: !kIsWeb && defaultTargetPlatform == TargetPlatform.android ? FutureBuilder( future: retrieveLostData(), builder: (BuildContext context, AsyncSnapshot snapshot) { diff --git a/packages/image_picker/image_picker/example/pubspec.yaml b/packages/image_picker/image_picker/example/pubspec.yaml index f3171aa7ccf8..93df8dfdc010 100755 --- a/packages/image_picker/image_picker/example/pubspec.yaml +++ b/packages/image_picker/image_picker/example/pubspec.yaml @@ -9,6 +9,7 @@ dependencies: flutter_plugin_android_lifecycle: ^1.0.2 image_picker: path: ../ + image_picker_for_web: ^0.1.0 dev_dependencies: flutter_driver: diff --git a/packages/image_picker/image_picker/example/web/favicon.png b/packages/image_picker/image_picker/example/web/favicon.png new file mode 100644 index 000000000000..8aaa46ac1ae2 Binary files /dev/null and b/packages/image_picker/image_picker/example/web/favicon.png differ diff --git a/packages/image_picker/image_picker/example/web/icons/Icon-192.png b/packages/image_picker/image_picker/example/web/icons/Icon-192.png new file mode 100644 index 000000000000..b749bfef0747 Binary files /dev/null and b/packages/image_picker/image_picker/example/web/icons/Icon-192.png differ diff --git a/packages/image_picker/image_picker/example/web/icons/Icon-512.png b/packages/image_picker/image_picker/example/web/icons/Icon-512.png new file mode 100644 index 000000000000..88cfd48dff11 Binary files /dev/null and b/packages/image_picker/image_picker/example/web/icons/Icon-512.png differ diff --git a/packages/image_picker/image_picker/example/web/index.html b/packages/image_picker/image_picker/example/web/index.html new file mode 100644 index 000000000000..787bbc72f6b1 --- /dev/null +++ b/packages/image_picker/image_picker/example/web/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + url_launcher web example + + + + + + + + diff --git a/packages/image_picker/image_picker/example/web/manifest.json b/packages/image_picker/image_picker/example/web/manifest.json new file mode 100644 index 000000000000..7d9c25627ebd --- /dev/null +++ b/packages/image_picker/image_picker/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "image_picker example", + "short_name": "image_picker", + "start_url": ".", + "display": "minimal-ui", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "An example of the image_picker on the web.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h index 38e5b56600f3..b6d8687a32e3 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.h @@ -7,7 +7,7 @@ @interface FLTImagePickerPlugin : NSObject // For testing only. -- (instancetype)initWithViewController:(UIViewController *)viewController; - (UIImagePickerController *)getImagePickerController; +- (UIViewController *)viewControllerWithWindow:(UIWindow *)window; @end diff --git a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m index 449157565e7f..00fdec245aaf 100644 --- a/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker/ios/Classes/FLTImagePickerPlugin.m @@ -25,7 +25,6 @@ @interface FLTImagePickerPlugin () *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/image_picker" binaryMessenger:[registrar messenger]]; - UIViewController *viewController = - [UIApplication sharedApplication].delegate.window.rootViewController; - FLTImagePickerPlugin *instance = - [[FLTImagePickerPlugin alloc] initWithViewController:viewController]; + FLTImagePickerPlugin *instance = [FLTImagePickerPlugin new]; [registrar addMethodCallDelegate:instance channel:channel]; } -- (instancetype)initWithViewController:(UIViewController *)viewController { - self = [super init]; - if (self) { - _viewController = viewController; - } - return self; -} - - (UIImagePickerController *)getImagePickerController { return _imagePickerController; } +- (UIViewController *)viewControllerWithWindow:(UIWindow *)window { + UIWindow *windowToUse = window; + if (windowToUse == nil) { + for (UIWindow *window in [UIApplication sharedApplication].windows) { + if (window.isKeyWindow) { + windowToUse = window; + break; + } + } + } + + UIViewController *topController = windowToUse.rootViewController; + while (topController.presentedViewController) { + topController = topController.presentedViewController; + } + return topController; +} + - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if (self.result) { self.result([FlutterError errorWithCode:@"multiple_request" @@ -136,7 +142,9 @@ - (void)showCamera { [UIImagePickerController isCameraDeviceAvailable:_device]) { _imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; _imagePickerController.cameraDevice = _device; - [_viewController presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController + animated:YES + completion:nil]; } else { [[[UIAlertView alloc] initWithTitle:@"Error" message:@"Camera not available." @@ -241,7 +249,9 @@ - (void)errorNoPhotoAccess:(PHAuthorizationStatus)status { - (void)showPhotoLibrary { // No need to check if SourceType is available. It always is. _imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; - [_viewController presentViewController:_imagePickerController animated:YES completion:nil]; + [[self viewControllerWithWindow:nil] presentViewController:_imagePickerController + animated:YES + completion:nil]; } - (void)imagePickerController:(UIImagePickerController *)picker diff --git a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m index e5c681e7d956..c8d5a2bb5368 100644 --- a/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker/ios/Tests/ImagePickerPluginTests.m @@ -7,6 +7,19 @@ @import image_picker; @import XCTest; +@interface MockViewController : UIViewController +@property(nonatomic, retain) UIViewController *mockPresented; +@end + +@implementation MockViewController +@synthesize mockPresented; + +- (UIViewController *)presentedViewController { + return mockPresented; +} + +@end + @interface FLTImagePickerPlugin (Test) @property(copy, nonatomic) FlutterResult result; - (void)handleSavedPath:(NSString *)path; @@ -23,8 +36,7 @@ - (void)testPluginPickImageDeviceBack { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; @@ -39,8 +51,7 @@ - (void)testPluginPickImageDeviceFront { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; @@ -55,8 +66,7 @@ - (void)testPluginPickVideoDeviceBack { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; @@ -71,8 +81,7 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; @@ -90,8 +99,7 @@ - (void)testPluginPickVideoDeviceFront { if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) { return; } - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(1)}]; @@ -104,8 +112,7 @@ - (void)testPluginPickVideoDeviceFront { #pragma mark - Test video duration - (void)testPickingVideoWithDuration { - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickVideo" arguments:@{@"source" : @(0), @"cameraDevice" : @(0), @"maxDuration" : @95}]; @@ -116,8 +123,7 @@ - (void)testPickingVideoWithDuration { } - (void)testPluginPickImageSelectMultipleTimes { - FLTImagePickerPlugin *plugin = - [[FLTImagePickerPlugin alloc] initWithViewController:[UIViewController new]]; + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"pickImage" arguments:@{@"source" : @(0), @"cameraDevice" : @(0)}]; @@ -131,4 +137,16 @@ - (void)testPluginPickImageSelectMultipleTimes { [plugin handleSavedPath:@"test"]; } +- (void)testViewController { + UIWindow *window = [UIWindow new]; + MockViewController *vc1 = [MockViewController new]; + window.rootViewController = vc1; + + UIViewController *vc2 = [UIViewController new]; + vc1.mockPresented = vc2; + + FLTImagePickerPlugin *plugin = [FLTImagePickerPlugin new]; + XCTAssertEqual([plugin viewControllerWithWindow:window], vc2); +} + @end diff --git a/packages/image_picker/image_picker/ios/image_picker.podspec b/packages/image_picker/image_picker/ios/image_picker.podspec index 47020f71711c..5c13cef272dd 100644 --- a/packages/image_picker/image_picker/ios/image_picker.podspec +++ b/packages/image_picker/image_picker/ios/image_picker.podspec @@ -18,7 +18,7 @@ Downloaded by pub (not CocoaPods). s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.platform = :ios, '8.0' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS' => 'armv7 arm64 x86_64' } + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'Tests/**/*' diff --git a/packages/image_picker/image_picker/pubspec.yaml b/packages/image_picker/image_picker/pubspec.yaml index 51069b6e6690..06da5297d80c 100755 --- a/packages/image_picker/image_picker/pubspec.yaml +++ b/packages/image_picker/image_picker/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker description: Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker -version: 0.6.7 +version: 0.6.7+4 flutter: plugin: diff --git a/packages/image_picker/image_picker_for_web/CHANGELOG.md b/packages/image_picker/image_picker_for_web/CHANGELOG.md new file mode 100644 index 000000000000..67320bcb3de2 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/CHANGELOG.md @@ -0,0 +1,7 @@ +# 0.1.0+1 + +* Remove `android` directory. + +# 0.1.0 + +* Initial open-source release. diff --git a/packages/image_picker/image_picker_for_web/LICENSE b/packages/image_picker/image_picker_for_web/LICENSE new file mode 100644 index 000000000000..0c382ce171cc --- /dev/null +++ b/packages/image_picker/image_picker_for_web/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium 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/image_picker/image_picker_for_web/README.md b/packages/image_picker/image_picker_for_web/README.md new file mode 100644 index 000000000000..81452e290984 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/README.md @@ -0,0 +1,92 @@ +# image_picker_for_web + +A web implementation of [`image_picker`][1]. + +## Browser Support + +Since Web Browsers don't offer direct access to their users' file system, +this plugin provides a `PickedFile` abstraction to make access access uniform +across platforms. + +The web version of the plugin puts network-accessible URIs as the `path` +in the returned `PickedFile`. + +### URL.createObjectURL() + +The `PickedFile` object in web is backed by [`URL.createObjectUrl` Web API](https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL), +which is reasonably well supported across all browsers: + +![Data on support for the bloburls feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/bloburls.png) + +However, the returned `path` attribute of the `PickedFile` points to a `network` resource, and not a +local path in your users' drive. See **Use the plugin** below for some examples on how to use this +return value in a cross-platform way. + +### input file "accept" + +In order to filter only video/image content, some browsers offer an [`accept` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) in their `input type="file"` form elements: + +![Data on support for the input-file-accept feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/input-file-accept.png) + +This feature is just a convenience for users, **not validation**. + +Users can override this setting on their browsers. You must validate in your app (or server) +that the user has picked the file type that you can handle. + +### input file "capture" + +In order to "take a photo", some mobile browsers offer a [`capture` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture): + +![Data on support for the html-media-capture feature across the major browsers from caniuse.com](https://caniuse.bitsofco.de/image/html-media-capture.png) + +Each browser may implement `capture` any way they please, so it may (or may not) make a +difference in your users' experience. + +## Usage + +### Import the package + +This package is an unendorsed web platform implementation of `image_picker`. + +In order to use this, you'll need to depend in `image_picker: ^0.6.7` (which was the first version of the plugin that allowed federation), and `image_picker_for_web: ^0.1.0`. + +```yaml +... +dependencies: + ... + image_picker: ^0.6.7 + image_picker_for_web: ^0.1.0 + ... +... +``` + +### Use the plugin + +You should be able to use `package:image_picker` _almost_ as normal. + +Once the user has picked a file, the returned `PickedFile` instance will contain a +`network`-accessible URL (pointing to a location within the browser). + +The instace will also let you retrieve the bytes of the selected file across all platforms. + +If you want to use the path directly, your code would need look like this: + +```dart +... +if (kIsWeb) { + Image.network(pickedFile.path); +} else { + Image.file(File(pickedFile.path)); +} +... +``` + +Or, using bytes: + +```dart +... +Image.memory(await pickedFile.readAsBytes()) +... +``` + +[1]: https://pub.dev/packages/image_picker diff --git a/packages/image_picker/image_picker_for_web/ios/image_picker_for_web.podspec b/packages/image_picker/image_picker_for_web/ios/image_picker_for_web.podspec new file mode 100644 index 000000000000..23fb795d1cc2 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/ios/image_picker_for_web.podspec @@ -0,0 +1,20 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'image_picker_for_web' + s.version = '0.0.1' + s.summary = 'No-op implementation of image_picker_for_web plugin to avoid build issues on iOS' + s.description = <<-DESC +temp fake image_picker_for_web plugin + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web' + s.license = { :file => '../LICENSE' } + s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + + s.ios.deployment_target = '8.0' +end diff --git a/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart new file mode 100644 index 000000000000..ce99dd6d5fc6 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/lib/image_picker_for_web.dart @@ -0,0 +1,184 @@ +import 'dart:async'; +import 'dart:html' as html; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:meta/meta.dart'; +import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; + +final String _kImagePickerInputsDomId = '__image_picker_web-file-input'; +final String _kAcceptImageMimeType = 'image/*'; +// TODO The value below seems to not be enough for Safari (https://github.com/flutter/flutter/issues/58532) +final String _kAcceptVideoMimeType = 'video/*'; + +/// The web implementation of [ImagePickerPlatform]. +/// +/// This class implements the `package:image_picker` functionality for the web. +class ImagePickerPlugin extends ImagePickerPlatform { + final ImagePickerPluginTestOverrides _overrides; + bool get _hasOverrides => _overrides != null; + + html.Element _target; + + /// A constructor that allows tests to override the function that creates file inputs. + ImagePickerPlugin({ + @visibleForTesting ImagePickerPluginTestOverrides overrides, + }) : _overrides = overrides { + _target = _ensureInitialized(_kImagePickerInputsDomId); + } + + /// Registers this class as the default instance of [ImagePickerPlatform]. + static void registerWith(Registrar registrar) { + ImagePickerPlatform.instance = ImagePickerPlugin(); + } + + @override + Future pickImage({ + @required ImageSource source, + double maxWidth, + double maxHeight, + int imageQuality, + CameraDevice preferredCameraDevice = CameraDevice.rear, + }) { + String capture = computeCaptureAttribute(source, preferredCameraDevice); + return pickFile(accept: _kAcceptImageMimeType, capture: capture); + } + + @override + Future pickVideo({ + @required ImageSource source, + CameraDevice preferredCameraDevice = CameraDevice.rear, + Duration maxDuration, + }) { + String capture = computeCaptureAttribute(source, preferredCameraDevice); + return pickFile(accept: _kAcceptVideoMimeType, capture: capture); + } + + /// Injects a file input with the specified accept+capture attributes, and + /// returns the PickedFile that the user selected locally. + /// + /// `capture` is only supported in mobile browsers. + /// See https://caniuse.com/#feat=html-media-capture + @visibleForTesting + Future pickFile({ + String accept, + String capture, + }) { + html.FileUploadInputElement input = createInputElement(accept, capture); + _injectAndActivate(input); + return _getSelectedFile(input); + } + + // DOM methods + + /// Converts plugin configuration into a proper value for the `capture` attribute. + /// + /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#capture + @visibleForTesting + String computeCaptureAttribute(ImageSource source, CameraDevice device) { + if (source == ImageSource.camera) { + return (device == CameraDevice.front) ? 'user' : 'environment'; + } + return null; + } + + html.File _getFileFromInput(html.FileUploadInputElement input) { + if (_hasOverrides) { + return _overrides.getFileFromInput(input); + } + return input?.files?.first; + } + + /// Handles the OnChange event from a FileUploadInputElement object + /// Returns the objectURL of the selected file. + String _handleOnChangeEvent(html.Event event) { + final html.FileUploadInputElement input = event?.target; + final html.File file = _getFileFromInput(input); + + if (file != null) { + return html.Url.createObjectUrl(file); + } + return null; + } + + /// Monitors an and returns the selected file. + Future _getSelectedFile(html.FileUploadInputElement input) { + final Completer _completer = Completer(); + // Observe the input until we can return something + input.onChange.first.then((event) { + final objectUrl = _handleOnChangeEvent(event); + if (!_completer.isCompleted) { + _completer.complete(PickedFile(objectUrl)); + } + }); + input.onError.first.then((event) { + if (!_completer.isCompleted) { + _completer.completeError(event); + } + }); + // Note that we don't bother detaching from these streams, since the + // "input" gets re-created in the DOM every time the user needs to + // pick a file. + return _completer.future; + } + + /// Initializes a DOM container where we can host input elements. + html.Element _ensureInitialized(String id) { + var target = html.querySelector('#${id}'); + if (target == null) { + final html.Element targetElement = + html.Element.tag('flt-image-picker-inputs')..id = id; + + html.querySelector('body').children.add(targetElement); + target = targetElement; + } + return target; + } + + /// Creates an input element that accepts certain file types, and + /// allows to `capture` from the device's cameras (where supported) + @visibleForTesting + html.Element createInputElement(String accept, String capture) { + if (_hasOverrides) { + return _overrides.createInputElement(accept, capture); + } + + html.Element element = html.FileUploadInputElement()..accept = accept; + + if (capture != null) { + element.setAttribute('capture', capture); + } + + return element; + } + + /// Injects the file input element, and clicks on it + void _injectAndActivate(html.Element element) { + _target.children.clear(); + _target.children.add(element); + element.click(); + } +} + +// Some tools to override behavior for unit-testing +/// A function that creates a file input with the passed in `accept` and `capture` attributes. +@visibleForTesting +typedef OverrideCreateInputFunction = html.Element Function( + String accept, + String capture, +); + +/// A function that extracts a [html.File] from the file `input` passed in. +@visibleForTesting +typedef OverrideExtractFilesFromInputFunction = html.File Function( + html.Element input, +); + +/// Overrides for some of the functionality above. +@visibleForTesting +class ImagePickerPluginTestOverrides { + /// Override the creation of the input element. + OverrideCreateInputFunction createInputElement; + + /// Override the extraction of the selected file from an input element. + OverrideExtractFilesFromInputFunction getFileFromInput; +} diff --git a/packages/image_picker/image_picker_for_web/pubspec.yaml b/packages/image_picker/image_picker_for_web/pubspec.yaml new file mode 100644 index 000000000000..29b9b185cb90 --- /dev/null +++ b/packages/image_picker/image_picker_for_web/pubspec.yaml @@ -0,0 +1,32 @@ +name: image_picker_for_web +description: Web platform implementation of image_picker +homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker/image_picker_for_web +# 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump +# the version to 2.0.0. +# See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 +version: 0.1.0+1 + +flutter: + plugin: + platforms: + web: + pluginClass: ImagePickerPlugin + fileName: image_picker_for_web.dart + +dependencies: + image_picker_platform_interface: ^1.1.0 + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + meta: ^1.1.7 + js: ^0.6.0 + +dev_dependencies: + flutter_test: + sdk: flutter + pedantic: ^1.8.0 + +environment: + sdk: ">=2.5.0 <3.0.0" + flutter: ">=1.10.0 <2.0.0" diff --git a/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart b/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart new file mode 100644 index 000000000000..96d048dd2a8e --- /dev/null +++ b/packages/image_picker/image_picker_for_web/test/image_picker_for_web_test.dart @@ -0,0 +1,84 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('chrome') // Uses dart:html + +import 'dart:convert'; +import 'dart:html' as html; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:image_picker_for_web/image_picker_for_web.dart'; +import 'package:image_picker_platform_interface/image_picker_platform_interface.dart'; + +final String expectedStringContents = "Hello, world!"; +final Uint8List bytes = utf8.encode(expectedStringContents); +final html.File textFile = html.File([bytes], "hello.txt"); + +void main() { + // Under test... + ImagePickerPlugin plugin; + + setUp(() { + plugin = ImagePickerPlugin(); + }); + + test('Can select a file', () async { + final mockInput = html.FileUploadInputElement(); + + final overrides = ImagePickerPluginTestOverrides() + ..createInputElement = ((_, __) => mockInput) + ..getFileFromInput = ((_) => textFile); + + final plugin = ImagePickerPlugin(overrides: overrides); + + // Init the pick file dialog... + final file = plugin.pickFile(); + + // Mock the browser behavior of selecting a file... + mockInput.dispatchEvent(html.Event('change')); + + // Now the file should be available + expect(file, completes); + // And readable + expect((await file).readAsBytes(), completion(isNotEmpty)); + }); + + // There's no good way of detecting when the user has "aborted" the selection. + + test('computeCaptureAttribute', () { + expect( + plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.front), + isNull, + ); + expect( + plugin.computeCaptureAttribute(ImageSource.gallery, CameraDevice.rear), + isNull, + ); + expect( + plugin.computeCaptureAttribute(ImageSource.camera, CameraDevice.front), + 'user', + ); + expect( + plugin.computeCaptureAttribute(ImageSource.camera, CameraDevice.rear), + 'environment', + ); + }); + + group('createInputElement', () { + test('accept: any, capture: null', () { + html.Element input = plugin.createInputElement('any', null); + + expect(input.attributes, containsPair('accept', 'any')); + expect(input.attributes, isNot(contains('capture'))); + }); + + test('accept: any, capture: something', () { + html.Element input = plugin.createInputElement('any', 'something'); + + expect(input.attributes, containsPair('accept', 'any')); + expect(input.attributes, containsPair('capture', 'something')); + }); + }); +} diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index b7b025cbef6f..ca935a3e12f4 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,13 @@ +## 0.3.4+2 + +* Fixed typo 'manuelly' for 'manually' in documentation. +* Updated CHANGELOG and pubspec.yaml to reflect the above. + +## 0.3.4+1 + +* iOS: Fix the bug that `SKPaymentQueueWrapper.transactions` doesn't return all transactions. +* iOS: Fix the app crashes if `InAppPurchaseConnection.instance` is called in the `main()`. + ## 0.3.4 * Expose SKError code to client apps. diff --git a/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m index 8bdb7f25f111..57370e16fcbb 100644 --- a/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m @@ -75,7 +75,7 @@ - (void)restoreTransactions:(nullable NSString *)applicationName { - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { - if (transaction.transactionIdentifier) { + if (transaction.transactionState != SKPaymentTransactionStatePurchasing) { // Use product identifier instead of transaction identifier for few reasons: // 1. Only transactions with purchased state and failed state will have a transaction id, it // will become impossible for clients to finish deferred transactions when needed. @@ -92,9 +92,7 @@ - (void)paymentQueue:(SKPaymentQueue *)queue - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { - if (transaction.transactionIdentifier) { - [self.transactionsSetter removeObjectForKey:transaction.payment.productIdentifier]; - } + [self.transactionsSetter removeObjectForKey:transaction.payment.productIdentifier]; } self.transactionsRemoved(transactions); } diff --git a/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m index bfe29f981396..06fe74a613ae 100644 --- a/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m @@ -187,7 +187,7 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result { result([FlutterError errorWithCode:@"storekit_duplicate_product_object" message:@"There is a pending transaction for the same product identifier. Please " - @"either wait for it to be finished or finish it manuelly using " + @"either wait for it to be finished or finish it manually using " @"`completePurchase` to avoid edge cases." details:call.arguments]); diff --git a/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m b/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m index e6a18e0acf58..f1290b074ad9 100644 --- a/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m +++ b/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m @@ -121,6 +121,7 @@ - (void)testAddPaymentWithSameProductIDWillFail { @"simulatesAskToBuyInSandBox" : @YES, }]; SKPaymentQueueStub* queue = [SKPaymentQueueStub new]; + queue.testState = SKPaymentTransactionStatePurchased; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray* _Nonnull transactions) { } @@ -145,7 +146,7 @@ - (void)testAddPaymentWithSameProductIDWillFail { XCTAssertEqualObjects( error.message, @"There is a pending transaction for the same product identifier. Please " - @"either wait for it to be finished or finish it manuelly using " + @"either wait for it to be finished or finish it manually using " @"`completePurchase` to avoid edge cases."); [expectation fulfill]; }]; diff --git a/packages/in_app_purchase/ios/Tests/PaymentQueueTest.m b/packages/in_app_purchase/ios/Tests/PaymentQueueTest.m index 2085ba328140..8f5b66496f69 100644 --- a/packages/in_app_purchase/ios/Tests/PaymentQueueTest.m +++ b/packages/in_app_purchase/ios/Tests/PaymentQueueTest.m @@ -66,6 +66,7 @@ - (void)testTransactionPurchased { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased); + XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); } - (void)testDuplicateTransactionsWillTriggerAnError { @@ -113,6 +114,7 @@ - (void)testTransactionFailed { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateFailed); + XCTAssertEqual(tran.transactionIdentifier, nil); } - (void)testTransactionRestored { @@ -140,6 +142,7 @@ - (void)testTransactionRestored { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored); + XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionPurchasing { @@ -167,6 +170,7 @@ - (void)testTransactionPurchasing { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchasing); + XCTAssertEqual(tran.transactionIdentifier, nil); } - (void)testTransactionDeferred { @@ -194,6 +198,7 @@ - (void)testTransactionDeferred { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateDeferred); + XCTAssertEqual(tran.transactionIdentifier, nil); } - (void)testFinishTransaction { diff --git a/packages/in_app_purchase/ios/Tests/Stubs.m b/packages/in_app_purchase/ios/Tests/Stubs.m index 2c3460f17f4b..58b77c14127d 100644 --- a/packages/in_app_purchase/ios/Tests/Stubs.m +++ b/packages/in_app_purchase/ios/Tests/Stubs.m @@ -215,7 +215,11 @@ - (instancetype)initWithMap:(NSDictionary *)map { - (instancetype)initWithState:(SKPaymentTransactionState)state { self = [super init]; if (self) { - [self setValue:@"fakeID" forKey:@"transactionIdentifier"]; + // Only purchased and restored transactions have transactionIdentifier: + // https://developer.apple.com/documentation/storekit/skpaymenttransaction/1411288-transactionidentifier?language=objc + if (state == SKPaymentTransactionStatePurchased || state == SKPaymentTransactionStateRestored) { + [self setValue:@"fakeID" forKey:@"transactionIdentifier"]; + } [self setValue:@(state) forKey:@"transactionState"]; } return self; @@ -224,7 +228,11 @@ - (instancetype)initWithState:(SKPaymentTransactionState)state { - (instancetype)initWithState:(SKPaymentTransactionState)state payment:(SKPayment *)payment { self = [super init]; if (self) { - [self setValue:@"fakeID" forKey:@"transactionIdentifier"]; + // Only purchased and restored transactions have transactionIdentifier: + // https://developer.apple.com/documentation/storekit/skpaymenttransaction/1411288-transactionidentifier?language=objc + if (state == SKPaymentTransactionStatePurchased || state == SKPaymentTransactionStateRestored) { + [self setValue:@"fakeID" forKey:@"transactionIdentifier"]; + } [self setValue:@(state) forKey:@"transactionState"]; _payment = payment; } diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart index 871879dca08e..da6fc7417585 100644 --- a/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart +++ b/packages/in_app_purchase/lib/src/in_app_purchase/app_store_connection.dart @@ -208,12 +208,14 @@ class _TransactionObserver implements SKTransactionObserverWrapper { return wrapper.transactionState == SKPaymentTransactionStateWrapper.restored; }).map((SKPaymentTransactionWrapper wrapper) => wrapper)); - return; } String receiptData = await getReceiptData(); purchaseUpdatedController - .add(transactions.map((SKPaymentTransactionWrapper transaction) { + .add(transactions.where((SKPaymentTransactionWrapper wrapper) { + return wrapper.transactionState != + SKPaymentTransactionStateWrapper.restored; + }).map((SKPaymentTransactionWrapper transaction) { PurchaseDetails purchaseDetails = PurchaseDetails.fromSKTransaction(transaction, receiptData); return purchaseDetails; diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart index 2079f69dce6c..ba3932f73878 100644 --- a/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart +++ b/packages/in_app_purchase/lib/src/in_app_purchase/in_app_purchase_connection.dart @@ -71,7 +71,7 @@ abstract class InAppPurchaseConnection { /// Enable the [InAppPurchaseConnection] to handle pending purchases. /// - /// Android Only: This method is required to be called when initialize the application. + /// This method is required to be called when initialize the application. /// It is to acknowledge your application has been updated to support pending purchases. /// See [Support pending transactions](https://developer.android.com/google/play/billing/billing_library_overview#pending) /// for more details. diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 49c438e40231..33d9281d3ce0 100644 --- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -37,9 +37,7 @@ class SKPaymentQueueWrapper { static final SKPaymentQueueWrapper _singleton = SKPaymentQueueWrapper._(); - SKPaymentQueueWrapper._() { - callbackChannel.setMethodCallHandler(_handleObserverCallbacks); - } + SKPaymentQueueWrapper._(); /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc) Future> transactions() async { @@ -59,6 +57,7 @@ class SKPaymentQueueWrapper { /// addTransactionObserver:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506042-addtransactionobserver?language=objc). void setTransactionObserver(SKTransactionObserverWrapper observer) { _observer = observer; + callbackChannel.setMethodCallHandler(_handleObserverCallbacks); } /// Posts a payment to the queue. diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index 12c7b45f7ddf..96432507a7af 100644 --- a/packages/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/pubspec.yaml @@ -1,7 +1,7 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase -version: 0.3.4 +version: 0.3.4+2 dependencies: async: ^2.0.8 diff --git a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart index cb2e0e7cad56..881e1fcc75b7 100644 --- a/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart +++ b/packages/in_app_purchase/test/in_app_purchase_connection/app_store_connection_test.dart @@ -90,6 +90,28 @@ void main() { expect(response.error, isNull); }); + test('queryPastPurchases should not block transaction updates', () async { + fakeIOSPlatform.transactions + .add(fakeIOSPlatform.createPurchasedTransactionWithProductID('foo')); + Completer completer = Completer(); + Stream> stream = + AppStoreConnection.instance.purchaseUpdatedStream; + + StreamSubscription subscription; + subscription = stream.listen((purchaseDetailsList) { + if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { + completer.complete(purchaseDetailsList); + subscription.cancel(); + } + }); + QueryPurchaseDetailsResponse response = + await AppStoreConnection.instance.queryPastPurchases(); + List result = await completer.future; + expect(result.length, 1); + expect(result.first.productID, 'foo'); + expect(response.error, isNull); + }); + test('should get empty result if there is no restored transactions', () async { fakeIOSPlatform.testRestoredTransactionsNull = true; @@ -328,10 +350,10 @@ class FakeIOSPlatform { SKPaymentTransactionWrapper createPendingTransactionWithProductID(String id) { return SKPaymentTransactionWrapper( + transactionIdentifier: null, payment: SKPaymentWrapper(productIdentifier: id), transactionState: SKPaymentTransactionStateWrapper.purchasing, transactionTimeStamp: 123123.121, - transactionIdentifier: id, error: null, originalTransaction: null); } @@ -349,10 +371,10 @@ class FakeIOSPlatform { SKPaymentTransactionWrapper createFailedTransactionWithProductID(String id) { return SKPaymentTransactionWrapper( + transactionIdentifier: null, payment: SKPaymentWrapper(productIdentifier: id), transactionState: SKPaymentTransactionStateWrapper.failed, transactionTimeStamp: 123123.121, - transactionIdentifier: id, error: SKError( code: 0, domain: 'ios_domain', diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart index 1dc70748f1db..c976e80a90a5 100644 --- a/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -22,6 +22,7 @@ final SKPaymentTransactionWrapper dummyOriginalTransaction = transactionIdentifier: '123123', error: dummyError, ); + final SKPaymentTransactionWrapper dummyTransaction = SKPaymentTransactionWrapper( transactionState: SKPaymentTransactionStateWrapper.purchased, diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md index a2df04613965..226d9ae01d0c 100644 --- a/packages/path_provider/path_provider/CHANGELOG.md +++ b/packages/path_provider/path_provider/CHANGELOG.md @@ -1,3 +1,6 @@ +## 1.6.11 +* Updated documentation to reflect the need for changes in testing for federated plugins + ## 1.6.10 * Linux implementation endorsement diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md index 944137f4631c..982a9f49cae5 100644 --- a/packages/path_provider/path_provider/README.md +++ b/packages/path_provider/path_provider/README.md @@ -2,7 +2,8 @@ [![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dartlang.org/packages/path_provider) -A Flutter plugin for finding commonly used locations on the filesystem. Supports iOS and Android. +A Flutter plugin for finding commonly used locations on the filesystem. Supports iOS, Android, Linux and MacOS. +Not all methods are supported on all platforms. ## Usage @@ -19,3 +20,17 @@ String appDocPath = appDocDir.path; ``` Please see the example app of this plugin for a full example. + +### Usage in tests + +`path_provider` now uses a `PlatformInterface`, meaning that not all platforms share the a single `PlatformChannel`-based implementation. +With that change, tests should be updated to mock `PathProviderPlatform` rather than `PlatformChannel`. + +See this `path_provider` [test](https://github.com/flutter/plugins/blob/master/packages/path_provider/path_provider/test/path_provider_test.dart) for an example. + +You will also have to temporarily add the following line to the setup of your test. +```dart +disablePathProviderPlatformOverride = true; +``` + +See this [issue](https://github.com/flutter/flutter/issues/52267), for more details on why this is needed. \ No newline at end of file diff --git a/packages/path_provider/path_provider/pubspec.yaml b/packages/path_provider/path_provider/pubspec.yaml index 98059e148140..d83911ddc2a8 100644 --- a/packages/path_provider/path_provider/pubspec.yaml +++ b/packages/path_provider/path_provider/pubspec.yaml @@ -2,7 +2,7 @@ name: path_provider description: Flutter plugin for getting commonly used locations on the Android & iOS file systems, such as the temp and app data directories. homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider/path_provider -version: 1.6.10 +version: 1.6.11 flutter: plugin: diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index 8c7ee24dfa6b..d17bbb96289b 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.8 + +* Support Linux by default. + ## 0.5.7+3 * Post-v2 Android embedding cleanup. diff --git a/packages/shared_preferences/shared_preferences/README.md b/packages/shared_preferences/shared_preferences/README.md index b5bf4050a8fd..9ccac0ac49ae 100644 --- a/packages/shared_preferences/shared_preferences/README.md +++ b/packages/shared_preferences/shared_preferences/README.md @@ -2,10 +2,10 @@ [![pub package](https://img.shields.io/pub/v/shared_preferences.svg)](https://pub.dartlang.org/packages/shared_preferences) -Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing -a persistent store for simple data. Data is persisted to disk asynchronously. -Neither platform can guarantee that writes will be persisted to disk after -returning and this plugin must not be used for storing critical data. +Wraps platform-specific persistent storage for simple data +(NSUserDefaults on iOS and macOS, SharedPreferences on Android, etc.). Data may be persisted to disk asynchronously, +and there is no guarantee that writes will be persisted to disk after +returning, so this plugin must not be used for storing critical data. **Please set your constraint to `shared_preferences: '>=0.5.y+x <2.0.0'`** diff --git a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart index 62160dee20fd..b8d3452a0a0e 100644 --- a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart +++ b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart @@ -3,10 +3,14 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io' show Platform; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:meta/meta.dart'; +import 'package:shared_preferences_linux/shared_preferences_linux.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing /// a persistent store for simple data. @@ -17,9 +21,26 @@ class SharedPreferences { static const String _prefix = 'flutter.'; static Completer _completer; + static bool _manualDartRegistrationNeeded = true; + + static SharedPreferencesStorePlatform get _store { + // This is to manually endorse the Linux implementation until automatic + // registration of dart plugins is implemented. For details see + // https://github.com/flutter/flutter/issues/52267. + if (_manualDartRegistrationNeeded) { + // Only do the initial registration if it hasn't already been overridden + // with a non-default instance. + if (!kIsWeb && + Platform.isLinux && + SharedPreferencesStorePlatform.instance + is MethodChannelSharedPreferencesStore) { + SharedPreferencesStorePlatform.instance = SharedPreferencesLinux(); + } + _manualDartRegistrationNeeded = false; + } - static SharedPreferencesStorePlatform get _store => - SharedPreferencesStorePlatform.instance; + return SharedPreferencesStorePlatform.instance; + } /// Loads and parses the [SharedPreferences] for this app from disk. /// diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index 2b278248745a..ca8c443cd753 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/shared_prefere # 0.5.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.5.7+3 +version: 0.5.8 flutter: plugin: @@ -15,6 +15,8 @@ flutter: pluginClass: SharedPreferencesPlugin ios: pluginClass: FLTSharedPreferencesPlugin + linux: + default_package: shared_preferences_linux macos: default_package: shared_preferences_macos web: @@ -30,6 +32,7 @@ dependencies: # validation, so we set a ^ constraint. # TODO(franciscojma): Revisit this (either update this part in the design or the pub tool). # https://github.com/flutter/flutter/issues/46264 + shared_preferences_linux: ^0.0.2 shared_preferences_macos: ^0.0.1 shared_preferences_web: ^0.1.2 diff --git a/packages/shared_preferences/shared_preferences_linux/.gitignore b/packages/shared_preferences/shared_preferences_linux/.gitignore new file mode 100644 index 000000000000..e9dc58d3d6e2 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/packages/shared_preferences/shared_preferences_linux/.metadata b/packages/shared_preferences/shared_preferences_linux/.metadata new file mode 100644 index 000000000000..9615744e96d1 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: e491544588e8d34fdf31d5f840b4649850ef167a + channel: master + +project_type: plugin diff --git a/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md new file mode 100644 index 000000000000..353a921ff281 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/CHANGELOG.md @@ -0,0 +1,8 @@ +## 0.0.2+1 +* Replace path_provider dependency with path_provider_linux. + +## 0.0.2 +* Add iOS stub. + +## 0.0.1 +* Initial release to support shared_preferences on Linux. diff --git a/packages/shared_preferences/shared_preferences_linux/LICENSE b/packages/shared_preferences/shared_preferences_linux/LICENSE new file mode 100644 index 000000000000..0c91662b3f2f --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2020 The Chromium 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/shared_preferences/shared_preferences_linux/README.md b/packages/shared_preferences/shared_preferences_linux/README.md new file mode 100644 index 000000000000..1894f50ae99e --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/README.md @@ -0,0 +1,22 @@ +# shared_preferences_linux + +The Linux implementation of [`shared_preferences`][1]. + +## Usage + +### Import the package + +This package is an unendorsed Linux implementation of `shared_preferences`. + +In order to use this now, you'll need to depend on `shared_preferences_linux`. +When this package is endorsed it will be automatically used by the `shared_preferences` package and you can switch to that API. + +```yaml +... +dependencies: + ... + shared_preferences_linux: ^0.0.1 + ... +``` + +[1]: ../ diff --git a/packages/shared_preferences/shared_preferences_linux/example/.gitignore b/packages/shared_preferences/shared_preferences_linux/example/.gitignore new file mode 100644 index 000000000000..1ba9c339effb --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/.gitignore @@ -0,0 +1,43 @@ +# 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 + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/shared_preferences/shared_preferences_linux/example/.metadata b/packages/shared_preferences/shared_preferences_linux/example/.metadata new file mode 100644 index 000000000000..c0bc9a90268a --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: e491544588e8d34fdf31d5f840b4649850ef167a + channel: master + +project_type: app diff --git a/packages/shared_preferences/shared_preferences_linux/example/README.md b/packages/shared_preferences/shared_preferences_linux/example/README.md new file mode 100644 index 000000000000..9d3bf1faf406 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/README.md @@ -0,0 +1,8 @@ +# shared_preferences_example + +Demonstrates how to use the shared_preferences plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart new file mode 100644 index 000000000000..ceacf2f95f28 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/lib/main.dart @@ -0,0 +1,87 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:shared_preferences_linux/shared_preferences_linux.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'SharedPreferences Demo', + home: SharedPreferencesDemo(), + ); + } +} + +class SharedPreferencesDemo extends StatefulWidget { + SharedPreferencesDemo({Key key}) : super(key: key); + + @override + SharedPreferencesDemoState createState() => SharedPreferencesDemoState(); +} + +class SharedPreferencesDemoState extends State { + final prefs = SharedPreferencesLinux.instance; + Future _counter; + + Future _incrementCounter() async { + final values = await prefs.getAll(); + final int counter = (values['counter'] as int ?? 0) + 1; + + setState(() { + _counter = prefs.setValue(null, "counter", counter).then((bool success) { + return counter; + }); + }); + } + + @override + void initState() { + super.initState(); + _counter = prefs.getAll().then((Map values) { + return (values['counter'] ?? 0); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("SharedPreferences Demo"), + ), + body: Center( + child: FutureBuilder( + future: _counter, + builder: (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + return const CircularProgressIndicator(); + default: + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return Text( + 'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n' + 'This should persist across restarts.', + ); + } + } + })), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/CMakeLists.txt b/packages/shared_preferences/shared_preferences_linux/example/linux/CMakeLists.txt new file mode 100644 index 000000000000..0236a8806654 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/CMakeLists.txt b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000000..94f43ff7fa6a --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + linux-x64 ${CMAKE_BUILD_TYPE} +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugin_registrant.cc b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000000..d38195aa0412 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,9 @@ +// +// Generated file. Do not edit. +// + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugin_registrant.h b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000000..9bf7478940c1 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugins.cmake b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..51436ae8c982 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,15 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/main.cc b/packages/shared_preferences/shared_preferences_linux/example/linux/main.cc new file mode 100644 index 000000000000..e7c5c5437037 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/my_application.cc b/packages/shared_preferences/shared_preferences_linux/example/linux/my_application.cc new file mode 100644 index 000000000000..c2357f17ea9c --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/my_application.cc @@ -0,0 +1,44 @@ +#include "my_application.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr)); +} diff --git a/packages/shared_preferences/shared_preferences_linux/example/linux/my_application.h b/packages/shared_preferences/shared_preferences_linux/example/linux/my_application.h new file mode 100644 index 000000000000..72271d5e4170 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml new file mode 100644 index 000000000000..8e726d642274 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/pubspec.yaml @@ -0,0 +1,26 @@ +name: shared_preferences_linux_example +description: Demonstrates how to use the shared_preferences_linux plugin. + +dependencies: + flutter: + sdk: flutter + shared_preferences: any + shared_preferences_linux: ^0.1.0 + +dependency_overrides: + shared_preferences_linux: + path: ../ + # Remove this override once the endorsement is published. + shared_preferences: + path: ../../shared_preferences/ + +dev_dependencies: + flutter_driver: + sdk: flutter + test: any + e2e: ^0.2.0 + pedantic: ^1.8.0 + +flutter: + uses-material-design: true + diff --git a/packages/shared_preferences/shared_preferences_linux/example/test_driver/shared_preferences_e2e.dart b/packages/shared_preferences/shared_preferences_linux/example/test_driver/shared_preferences_e2e.dart new file mode 100644 index 000000000000..b693df2131ed --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/test_driver/shared_preferences_e2e.dart @@ -0,0 +1,89 @@ +import 'dart:async'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + group('$SharedPreferences', () { + const Map kTestValues = { + 'flutter.String': 'hello world', + 'flutter.bool': true, + 'flutter.int': 42, + 'flutter.double': 3.14159, + 'flutter.List': ['foo', 'bar'], + }; + + const Map kTestValues2 = { + 'flutter.String': 'goodbye world', + 'flutter.bool': false, + 'flutter.int': 1337, + 'flutter.double': 2.71828, + 'flutter.List': ['baz', 'quox'], + }; + + SharedPreferences preferences; + + setUp(() async { + preferences = await SharedPreferences.getInstance(); + }); + + tearDown(() { + preferences.clear(); + }); + + test('reading', () async { + expect(preferences.get('String'), isNull); + expect(preferences.get('bool'), isNull); + expect(preferences.get('int'), isNull); + expect(preferences.get('double'), isNull); + expect(preferences.get('List'), isNull); + expect(preferences.getString('String'), isNull); + expect(preferences.getBool('bool'), isNull); + expect(preferences.getInt('int'), isNull); + expect(preferences.getDouble('double'), isNull); + expect(preferences.getStringList('List'), isNull); + }); + + test('writing', () async { + await Future.wait(>[ + preferences.setString('String', kTestValues2['flutter.String']), + preferences.setBool('bool', kTestValues2['flutter.bool']), + preferences.setInt('int', kTestValues2['flutter.int']), + preferences.setDouble('double', kTestValues2['flutter.double']), + preferences.setStringList('List', kTestValues2['flutter.List']) + ]); + expect(preferences.getString('String'), kTestValues2['flutter.String']); + expect(preferences.getBool('bool'), kTestValues2['flutter.bool']); + expect(preferences.getInt('int'), kTestValues2['flutter.int']); + expect(preferences.getDouble('double'), kTestValues2['flutter.double']); + expect(preferences.getStringList('List'), kTestValues2['flutter.List']); + }); + + test('removing', () async { + const String key = 'testKey'; + await preferences.setString(key, kTestValues['flutter.String']); + await preferences.setBool(key, kTestValues['flutter.bool']); + await preferences.setInt(key, kTestValues['flutter.int']); + await preferences.setDouble(key, kTestValues['flutter.double']); + await preferences.setStringList(key, kTestValues['flutter.List']); + await preferences.remove(key); + expect(preferences.get('testKey'), isNull); + }); + + test('clearing', () async { + await preferences.setString('String', kTestValues['flutter.String']); + await preferences.setBool('bool', kTestValues['flutter.bool']); + await preferences.setInt('int', kTestValues['flutter.int']); + await preferences.setDouble('double', kTestValues['flutter.double']); + await preferences.setStringList('List', kTestValues['flutter.List']); + await preferences.clear(); + expect(preferences.getString('String'), null); + expect(preferences.getBool('bool'), null); + expect(preferences.getInt('int'), null); + expect(preferences.getDouble('double'), null); + expect(preferences.getStringList('List'), null); + }); + }); +} diff --git a/packages/shared_preferences/shared_preferences_linux/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences_linux/example/test_driver/shared_preferences_e2e_test.dart new file mode 100644 index 000000000000..f3aa9e218d82 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/example/test_driver/shared_preferences_e2e_test.dart @@ -0,0 +1,15 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + await driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/shared_preferences/shared_preferences_linux/ios/shared_preferences_linux.podspec b/packages/shared_preferences/shared_preferences_linux/ios/shared_preferences_linux.podspec new file mode 100644 index 000000000000..8f4d3cdddcd5 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/ios/shared_preferences_linux.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint shared_preferences_launcher_linux.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'shared_preferences_linux' + s.version = '0.0.1' + s.summary = 'shared_preferences_linux iOS stub' + s.description = <<-DESC + No-op implementation of the Linux shared_preferences plugin to avoid build issues on iOS + DESC + s.homepage = 'https://github.com/flutter/plugins' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_linux' } + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.swift_version = '5.0' +end diff --git a/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart new file mode 100644 index 000000000000..c975ad1a7544 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/lib/shared_preferences_linux.dart @@ -0,0 +1,97 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert' show json; + +import 'package:file/file.dart'; +import 'package:file/local.dart'; +import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider_linux/path_provider_linux.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; + +/// The Linux implementation of [SharedPreferencesStorePlatform]. +/// +/// This class implements the `package:shared_preferences` functionality for Linux. +class SharedPreferencesLinux extends SharedPreferencesStorePlatform { + /// The default instance of [SharedPreferencesLinux] to use. + static SharedPreferencesLinux instance = SharedPreferencesLinux(); + + /// Local copy of preferences + Map _cachedPreferences; + + /// File system used to store to disk. Exposed for testing only. + @visibleForTesting + FileSystem fs = LocalFileSystem(); + + /// Gets the file where the preferences are stored. + Future _getLocalDataFile() async { + final pathProvider = PathProviderLinux(); + final directory = await pathProvider.getApplicationSupportPath(); + return fs.file(path.join(directory, 'shared_preferences.json')); + } + + /// Gets the preferences from the stored file. Once read, the preferences are + /// maintained in memory. + Future> _readPreferences() async { + if (_cachedPreferences != null) { + return _cachedPreferences; + } + + _cachedPreferences = {}; + var localDataFile = await _getLocalDataFile(); + if (localDataFile.existsSync()) { + String stringMap = localDataFile.readAsStringSync(); + if (stringMap.isNotEmpty) { + _cachedPreferences = json.decode(stringMap) as Map; + } + } + + return _cachedPreferences; + } + + /// Writes the cached preferences to disk. Returns [true] if the operation + /// succeeded. + Future _writePreferences(Map preferences) async { + try { + var localDataFile = await _getLocalDataFile(); + if (!localDataFile.existsSync()) { + localDataFile.createSync(recursive: true); + } + var stringMap = json.encode(preferences); + localDataFile.writeAsStringSync(stringMap); + } catch (e) { + print("Error saving preferences to disk: $e"); + return false; + } + return true; + } + + @override + Future clear() async { + var preferences = await _readPreferences(); + preferences.clear(); + return _writePreferences(preferences); + } + + @override + Future> getAll() async { + return _readPreferences(); + } + + @override + Future remove(String key) async { + var preferences = await _readPreferences(); + preferences.remove(key); + return _writePreferences(preferences); + } + + @override + Future setValue(String valueType, String key, Object value) async { + var preferences = await _readPreferences(); + preferences[key] = value; + return _writePreferences(preferences); + } +} diff --git a/packages/shared_preferences/shared_preferences_linux/pubspec.yaml b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml new file mode 100644 index 000000000000..3d40d39241ac --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/pubspec.yaml @@ -0,0 +1,29 @@ +name: shared_preferences_linux +description: Linux implementation of the shared_preferences plugin +version: 0.0.2+1 +homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_linux + +flutter: + plugin: + platforms: + linux: + dartPluginClass: SharedPreferencesLinux + pluginClass: none + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.8 <2.0.0" + +dependencies: + file: ^5.1.0 + flutter: + sdk: flutter + meta: ^1.0.4 + path: ^1.6.4 + path_provider_linux: ^0.0.1 + shared_preferences_platform_interface: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + pedantic: ^1.8.0 diff --git a/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart new file mode 100644 index 000000000000..8c659f212aa5 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_linux/test/shared_preferences_linux_test.dart @@ -0,0 +1,77 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +import 'package:file/memory.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider_linux/path_provider_linux.dart'; +import 'package:shared_preferences_linux/shared_preferences_linux.dart'; + +MemoryFileSystem fs; + +void main() { + setUp(() { + fs = MemoryFileSystem.test(); + }); + + tearDown(() {}); + + Future _getFilePath() async { + final pathProvider = PathProviderLinux(); + final directory = await pathProvider.getApplicationSupportPath(); + return path.join(directory, 'shared_preferences.json'); + } + + _writeTestFile(String value) async { + fs.file(await _getFilePath()) + ..createSync(recursive: true) + ..writeAsStringSync(value); + } + + Future _readTestFile() async { + return fs.file(await _getFilePath()).readAsStringSync(); + } + + SharedPreferencesLinux _getPreferences() { + var prefs = SharedPreferencesLinux(); + prefs.fs = fs; + return prefs; + } + + test('getAll', () async { + await _writeTestFile('{"key1": "one", "key2": 2}'); + var prefs = _getPreferences(); + + var values = await prefs.getAll(); + expect(values, hasLength(2)); + expect(values['key1'], 'one'); + expect(values['key2'], 2); + }); + + test('remove', () async { + await _writeTestFile('{"key1":"one","key2":2}'); + var prefs = _getPreferences(); + + await prefs.remove('key2'); + + expect(await _readTestFile(), '{"key1":"one"}'); + }); + + test('setValue', () async { + await _writeTestFile('{}'); + var prefs = _getPreferences(); + + await prefs.setValue('', 'key1', 'one'); + await prefs.setValue('', 'key2', 2); + + expect(await _readTestFile(), '{"key1":"one","key2":2}'); + }); + + test('clear', () async { + await _writeTestFile('{"key1":"one","key2":2}'); + var prefs = _getPreferences(); + + await prefs.clear(); + expect(await _readTestFile(), '{}'); + }); +} diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 2855eade4d21..a5364726bee3 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,11 @@ +## 5.5.0 + +* Support Linux by default. + +## 5.4.11 + +* Add documentation in README suggesting how to properly encode urls with special characters. + ## 5.4.10 * Post-v2 Android embedding cleanups. @@ -42,7 +50,7 @@ ## 5.4.0 -* Support macos by default. +* Support macOS by default. ## 5.3.0 @@ -68,7 +76,7 @@ ## 5.2.3 -Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. +* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. ## 5.2.2 diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md index 2dcc6359c1af..53c7fe96cde7 100644 --- a/packages/url_launcher/url_launcher/README.md +++ b/packages/url_launcher/url_launcher/README.md @@ -53,6 +53,27 @@ Common schemes supported by both iOS and Android: More details can be found here for [iOS](https://developer.apple.com/library/content/featuredarticles/iPhoneURLScheme_Reference/Introduction/Introduction.html) and [Android](https://developer.android.com/guide/components/intents-common.html) +### Encoding URLs + +URLs must be properly encoded, especially when including spaces or other special characters. This can be done using the [`Uri` class](https://api.dart.dev/stable/2.7.1/dart-core/Uri-class.html): +```dart +import 'dart:core'; +import 'package:url_launcher/url_launcher.dart'; + +final Uri _emailLaunchUri = Uri( + scheme: 'mailto', + path: 'smith@example.com', + queryParameters: { + 'subject': 'Example Subject & Symbols are allowed!' + } +); + +// ... + +// mailto:smith@example.com?subject=Example+Subject+%26+Symbols+are+allowed%21 +launch(_emailLaunchUri.toString()); +``` + ## Handling missing URL receivers A particular mobile device may not be able to receive all supported URL schemes. diff --git a/packages/url_launcher/url_launcher/example/linux/CMakeLists.txt b/packages/url_launcher/url_launcher/example/linux/CMakeLists.txt new file mode 100644 index 000000000000..0236a8806654 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/url_launcher/url_launcher/example/linux/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000000..94f43ff7fa6a --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + linux-x64 ${CMAKE_BUILD_TYPE} +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000000..026851fa2f96 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.h b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000000..9bf7478940c1 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugins.cmake b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..1fc8ed344297 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/url_launcher/url_launcher/example/linux/main.cc b/packages/url_launcher/url_launcher/example/linux/main.cc new file mode 100644 index 000000000000..e7c5c5437037 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/url_launcher/url_launcher/example/linux/my_application.cc b/packages/url_launcher/url_launcher/example/linux/my_application.cc new file mode 100644 index 000000000000..c2357f17ea9c --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/my_application.cc @@ -0,0 +1,44 @@ +#include "my_application.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr)); +} diff --git a/packages/url_launcher/url_launcher/example/linux/my_application.h b/packages/url_launcher/url_launcher/example/linux/my_application.h new file mode 100644 index 000000000000..72271d5e4170 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart index e1d75f93b326..6cf1168c81bd 100644 --- a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart +++ b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart @@ -2,6 +2,9 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter_test/flutter_test.dart'; import 'package:e2e/e2e.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -15,8 +18,10 @@ void main() { // Generally all devices should have some default browser. expect(await canLaunch('http://flutter.dev'), true); - // Generally all devices should have some default SMS app. - expect(await canLaunch('sms:5555555555'), true); + // SMS handling is available by default on most platforms. + if (kIsWeb || !Platform.isLinux) { + expect(await canLaunch('sms:5555555555'), true); + } // tel: and mailto: links may not be openable on every device. iOS // simulators notably can't open these link types. diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 156d608583a5..299de938165f 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher description: Flutter plugin for launching a URL on Android and iOS. Supports web, phone, SMS, and email schemes. homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher -version: 5.4.10 +version: 5.5.0 flutter: plugin: @@ -14,6 +14,8 @@ flutter: pluginClass: FLTURLLauncherPlugin web: default_package: url_launcher_web + linux: + default_package: url_laucher_linux macos: default_package: url_laucher_macos @@ -27,6 +29,7 @@ dependencies: # TODO(amirh): Revisit this (either update this part in the design or the pub tool). # https://github.com/flutter/flutter/issues/46264 url_launcher_web: ^0.1.0+1 + url_launcher_linux: ^0.0.1 url_launcher_macos: ^0.0.1 dev_dependencies: diff --git a/packages/url_launcher/url_launcher_linux/.gitignore b/packages/url_launcher/url_launcher_linux/.gitignore new file mode 100644 index 000000000000..53e92cc4181f --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/.gitignore @@ -0,0 +1,3 @@ +.packages +.flutter-plugins +pubspec.lock diff --git a/packages/url_launcher/url_launcher_linux/.metadata b/packages/url_launcher/url_launcher_linux/.metadata new file mode 100644 index 000000000000..457a92ae1645 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4b12050112afd581ddf53df848275fa681f908f3 + channel: master + +project_type: plugin diff --git a/packages/url_launcher/url_launcher_linux/CHANGELOG.md b/packages/url_launcher/url_launcher_linux/CHANGELOG.md new file mode 100644 index 000000000000..e03b1d387c55 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/CHANGELOG.md @@ -0,0 +1,5 @@ +# 0.0.1+1 +* README update for endorsement by url_launcher. + +# 0.0.1 +* The initial implementation of url_launcher for Linux diff --git a/packages/url_launcher/url_launcher_linux/LICENSE b/packages/url_launcher/url_launcher_linux/LICENSE new file mode 100644 index 000000000000..0c91662b3f2f --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2020 The Chromium 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_linux/README.md b/packages/url_launcher/url_launcher_linux/README.md new file mode 100644 index 000000000000..0474c58da40e --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/README.md @@ -0,0 +1,34 @@ +# url_launcher_linux + +The Linux implementation of [`url_launcher`][1]. + +## Usage + +### Import the package + +This package has been endorsed, meaning that you only need to add `url_launcher` +as a dependency in your `pubspec.yaml`. It will be automatically included in your app +when you depend on `package:url_launcher`. + +This is what the above means to your `pubspec.yaml`: + +```yaml +... +dependencies: + ... + url_launcher: ^5.5.0 + ... +``` + +If you wish to use the Linux package only, you can add `url_launcher_linux` as a +dependency: + +```yaml +... +dependencies: + ... + url_launcher_linux: ^0.0.1 + ... +``` + +[1]: ../ diff --git a/packages/url_launcher/url_launcher_linux/example/.gitignore b/packages/url_launcher/url_launcher_linux/example/.gitignore new file mode 100644 index 000000000000..f3c205341e7d --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/.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/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/url_launcher/url_launcher_linux/example/.metadata b/packages/url_launcher/url_launcher_linux/example/.metadata new file mode 100644 index 000000000000..99b1a7456d66 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4b12050112afd581ddf53df848275fa681f908f3 + channel: master + +project_type: app diff --git a/packages/url_launcher/url_launcher_linux/example/README.md b/packages/url_launcher/url_launcher_linux/example/README.md new file mode 100644 index 000000000000..28dd90d71700 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/README.md @@ -0,0 +1,8 @@ +# url_launcher_example + +Demonstrates how to use the url_launcher plugin. + +## Getting Started + +For help getting started with Flutter, view our online +[documentation](http://flutter.io/). diff --git a/packages/url_launcher/url_launcher_linux/example/lib/main.dart b/packages/url_launcher/url_launcher_linux/example/lib/main.dart new file mode 100644 index 000000000000..b5cce7482d07 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/lib/main.dart @@ -0,0 +1,194 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs + +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'URL Launcher', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'URL Launcher'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + Future _launched; + String _phone = ''; + + Future _launchInBrowser(String url) async { + if (await canLaunch(url)) { + await launch( + url, + forceSafariVC: false, + forceWebView: false, + headers: {'my_header_key': 'my_header_value'}, + ); + } else { + throw 'Could not launch $url'; + } + } + + Future _launchInWebViewOrVC(String url) async { + if (await canLaunch(url)) { + await launch( + url, + forceSafariVC: true, + forceWebView: true, + headers: {'my_header_key': 'my_header_value'}, + ); + } else { + throw 'Could not launch $url'; + } + } + + Future _launchInWebViewWithJavaScript(String url) async { + if (await canLaunch(url)) { + await launch( + url, + forceSafariVC: true, + forceWebView: true, + enableJavaScript: true, + ); + } else { + throw 'Could not launch $url'; + } + } + + Future _launchInWebViewWithDomStorage(String url) async { + if (await canLaunch(url)) { + await launch( + url, + forceSafariVC: true, + forceWebView: true, + enableDomStorage: true, + ); + } else { + throw 'Could not launch $url'; + } + } + + Future _launchUniversalLinkIos(String url) async { + if (await canLaunch(url)) { + final bool nativeAppLaunchSucceeded = await launch( + url, + forceSafariVC: false, + universalLinksOnly: true, + ); + if (!nativeAppLaunchSucceeded) { + await launch( + url, + forceSafariVC: true, + ); + } + } + } + + Widget _launchStatus(BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } else { + return const Text(''); + } + } + + Future _makePhoneCall(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + } + + @override + Widget build(BuildContext context) { + 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')), + ), + RaisedButton( + onPressed: () => setState(() { + _launched = _makePhoneCall('tel:$_phone'); + }), + child: const Text('Make phone call'), + ), + const Padding( + padding: EdgeInsets.all(16.0), + child: Text(toLaunch), + ), + RaisedButton( + onPressed: () => setState(() { + _launched = _launchInBrowser(toLaunch); + }), + child: const Text('Launch in browser'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + RaisedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewOrVC(toLaunch); + }), + child: const Text('Launch in app'), + ), + RaisedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewWithJavaScript(toLaunch); + }), + child: const Text('Launch in app(JavaScript ON)'), + ), + RaisedButton( + onPressed: () => setState(() { + _launched = _launchInWebViewWithDomStorage(toLaunch); + }), + child: const Text('Launch in app(DOM storage ON)'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + RaisedButton( + onPressed: () => setState(() { + _launched = _launchUniversalLinkIos(toLaunch); + }), + child: const Text( + 'Launch a universal link in a native app, fallback to Safari.(Youtube)'), + ), + const Padding(padding: EdgeInsets.all(16.0)), + FutureBuilder(future: _launched, builder: _launchStatus), + ], + ), + ], + ), + ); + } +} diff --git a/packages/url_launcher/url_launcher_linux/example/linux/.gitignore b/packages/url_launcher/url_launcher_linux/example/linux/.gitignore new file mode 100644 index 000000000000..d3896c98444f --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt new file mode 100644 index 000000000000..0236a8806654 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000000..94f43ff7fa6a --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + linux-x64 ${CMAKE_BUILD_TYPE} +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000000..026851fa2f96 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000000..9bf7478940c1 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,13 @@ +// +// Generated file. Do not edit. +// + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..1fc8ed344297 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,16 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/url_launcher/url_launcher_linux/example/linux/main.cc b/packages/url_launcher/url_launcher_linux/example/linux/main.cc new file mode 100644 index 000000000000..e7c5c5437037 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc b/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc new file mode 100644 index 000000000000..c2357f17ea9c --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/my_application.cc @@ -0,0 +1,44 @@ +#include "my_application.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), nullptr)); +} diff --git a/packages/url_launcher/url_launcher_linux/example/linux/my_application.h b/packages/url_launcher/url_launcher_linux/example/linux/my_application.h new file mode 100644 index 000000000000..72271d5e4170 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/url_launcher/url_launcher_linux/example/pubspec.yaml b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml new file mode 100644 index 000000000000..a4d08f30d6b4 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/pubspec.yaml @@ -0,0 +1,18 @@ +name: url_launcher_example +description: Demonstrates how to use the url_launcher plugin. + +dependencies: + flutter: + sdk: flutter + url_launcher: any + url_launcher_linux: + path: ../ + +dev_dependencies: + e2e: "^0.2.0" + flutter_driver: + sdk: flutter + pedantic: ^1.8.0 + +flutter: + uses-material-design: true diff --git a/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart new file mode 100644 index 000000000000..516835cec33b --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e.dart @@ -0,0 +1,23 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:e2e/e2e.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + test('canLaunch', () async { + expect(await canLaunch('randomstring'), false); + + // Generally all devices should have some default browser. + expect(await canLaunch('http://flutter.dev'), true); + + // Desktop will not necessarily support sms:. + + // tel: and mailto: links may not be openable on every device. iOS + // simulators notably can't open these link types. + }); +} diff --git a/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart new file mode 100644 index 000000000000..1bcd0d37f450 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/example/test_driver/url_launcher_e2e_test.dart @@ -0,0 +1,15 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + await driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/url_launcher/url_launcher_linux/ios/.gitignore b/packages/url_launcher/url_launcher_linux/ios/.gitignore new file mode 100644 index 000000000000..aa479fd3ce8a --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec b/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec new file mode 100644 index 000000000000..1359fd403d8d --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/ios/url_launcher_linux.podspec @@ -0,0 +1,22 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint url_launcher_linux.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'url_launcher_linux' + s.version = '0.0.1' + s.summary = 'url_launcher_linux iOS stub' + s.description = <<-DESC + No-op implementation of the Linux url_launcher plugin to avoid build issues on iOS + DESC + s.homepage = 'https://github.com/flutter/plugins' + s.license = { :type => 'BSD', :file => '../LICENSE' } + s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } + s.source = { :http => 'https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux' } + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.swift_version = '5.0' +end diff --git a/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart new file mode 100644 index 000000000000..18f7af1836ce --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/lib/url_launcher_linux.dart @@ -0,0 +1,3 @@ +// The url_launcher_platform_interface defaults to MethodChannelUrlLauncher +// as its instance, which is all the Linux implementation needs. This file +// is here to silence warnings when publishing to pub. diff --git a/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt new file mode 100644 index 000000000000..1403d0cbc9e4 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/linux/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "url_launcher_linux") +project(${PROJECT_NAME} LANGUAGES CXX) + +set(PLUGIN_NAME "${PROJECT_NAME}_plugin") + +add_library(${PLUGIN_NAME} SHARED + "url_launcher_plugin.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) diff --git a/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h b/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h new file mode 100644 index 000000000000..efcfe62e706a --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/linux/include/url_launcher_linux/url_launcher_plugin.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_ +#define PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_ + +// A plugin to launch URLs. + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +G_DECLARE_FINAL_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, FL, + URL_LAUNCHER_PLUGIN, GObject) + +FLUTTER_PLUGIN_EXPORT FlUrlLauncherPlugin* fl_url_launcher_plugin_new( + FlPluginRegistrar* registrar); + +FLUTTER_PLUGIN_EXPORT void url_launcher_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // PACKAGES_URL_LAUNCHER_URL_LAUNCHER_LINUX_LINUX_INCLUDE_URL_LAUNCHER_URL_LAUNCHER_PLUGIN_H_ diff --git a/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc new file mode 100644 index 000000000000..592bb965e83f --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/linux/url_launcher_plugin.cc @@ -0,0 +1,148 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "include/url_launcher_linux/url_launcher_plugin.h" + +#include +#include + +// See url_launcher_channel.dart for documentation. +const char kChannelName[] = "plugins.flutter.io/url_launcher"; +const char kBadArgumentsError[] = "Bad Arguments"; +const char kLaunchError[] = "Launch Error"; +const char kCanLaunchMethod[] = "canLaunch"; +const char kLaunchMethod[] = "launch"; +const char kUrlKey[] = "url"; + +struct _FlUrlLauncherPlugin { + GObject parent_instance; + + FlPluginRegistrar* registrar; + + // Connection to Flutter engine. + FlMethodChannel* channel; +}; + +G_DEFINE_TYPE(FlUrlLauncherPlugin, fl_url_launcher_plugin, g_object_get_type()) + +// Gets the URL from the arguments or generates an error. +static gchar* get_url(FlValue* args, GError** error) { + if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { + g_set_error(error, 0, 0, "Argument map missing or malformed"); + return nullptr; + } + FlValue* url_value = fl_value_lookup_string(args, kUrlKey); + if (url_value == nullptr) { + g_set_error(error, 0, 0, "Missing URL"); + return nullptr; + } + + return g_strdup(fl_value_get_string(url_value)); +} + +// Called to check if a URL can be launched. +static FlMethodResponse* can_launch(FlUrlLauncherPlugin* self, FlValue* args) { + g_autoptr(GError) error = nullptr; + g_autofree gchar* url = get_url(args, &error); + if (url == nullptr) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, error->message, nullptr)); + } + + gboolean is_launchable = FALSE; + g_autofree gchar* scheme = g_uri_parse_scheme(url); + if (scheme != nullptr) { + g_autoptr(GAppInfo) app_info = + g_app_info_get_default_for_uri_scheme(scheme); + is_launchable = app_info != nullptr; + } + + g_autoptr(FlValue) result = fl_value_new_bool(is_launchable); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +// Called when a URL should launch. +static FlMethodResponse* launch(FlUrlLauncherPlugin* self, FlValue* args) { + g_autoptr(GError) error = nullptr; + g_autofree gchar* url = get_url(args, &error); + if (url == nullptr) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + kBadArgumentsError, error->message, nullptr)); + } + + FlView* view = fl_plugin_registrar_get_view(self->registrar); + gboolean launched; + if (view != nullptr) { + GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))); + launched = gtk_show_uri_on_window(window, url, GDK_CURRENT_TIME, &error); + } else { + launched = g_app_info_launch_default_for_uri(url, nullptr, &error); + } + if (!launched) { + g_autofree gchar* message = + g_strdup_printf("Failed to launch URL: %s", error->message); + return FL_METHOD_RESPONSE( + fl_method_error_response_new(kLaunchError, message, nullptr)); + } + + g_autoptr(FlValue) result = fl_value_new_bool(TRUE); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +// Called when a method call is received from Flutter. +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + FlValue* args = fl_method_call_get_args(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kCanLaunchMethod) == 0) + response = can_launch(self, args); + else if (strcmp(method, kLaunchMethod) == 0) + response = launch(self, args); + else + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) + g_warning("Failed to send method call response: %s", error->message); +} + +static void fl_url_launcher_plugin_dispose(GObject* object) { + FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN(object); + + g_clear_object(&self->registrar); + g_clear_object(&self->channel); + + G_OBJECT_CLASS(fl_url_launcher_plugin_parent_class)->dispose(object); +} + +static void fl_url_launcher_plugin_class_init(FlUrlLauncherPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_url_launcher_plugin_dispose; +} + +FlUrlLauncherPlugin* fl_url_launcher_plugin_new(FlPluginRegistrar* registrar) { + FlUrlLauncherPlugin* self = FL_URL_LAUNCHER_PLUGIN( + g_object_new(fl_url_launcher_plugin_get_type(), nullptr)); + + self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_cb, + g_object_ref(self), g_object_unref); + + return self; +} + +static void fl_url_launcher_plugin_init(FlUrlLauncherPlugin* self) {} + +void url_launcher_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + FlUrlLauncherPlugin* plugin = fl_url_launcher_plugin_new(registrar); + g_object_unref(plugin); +} diff --git a/packages/url_launcher/url_launcher_linux/pubspec.yaml b/packages/url_launcher/url_launcher_linux/pubspec.yaml new file mode 100644 index 000000000000..74c2968aba69 --- /dev/null +++ b/packages/url_launcher/url_launcher_linux/pubspec.yaml @@ -0,0 +1,19 @@ +name: url_launcher_linux +description: Linux implementation of the url_launcher plugin. +version: 0.0.1+1 +homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_linux + +flutter: + plugin: + platforms: + linux: + pluginClass: UrlLauncherPlugin + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.8 <2.0.0" + +dependencies: + flutter: + sdk: flutter + diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index df1b2c97b744..e9f7bde63fda 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.1.2 + +- Adds "tel" and "sms" support + # 0.1.1+6 - Open "mailto" urls with target set as "\_top" on Safari browsers. diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart index e55ceb2269bc..1bac4d524122 100644 --- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart +++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart @@ -7,7 +7,11 @@ import 'package:url_launcher_platform_interface/url_launcher_platform_interface. import 'package:platform_detect/platform_detect.dart' show browser; -const _mailtoScheme = 'mailto'; +const _safariTargetTopSchemes = { + 'mailto', + 'tel', + 'sms', +}; /// The web implementation of [UrlLauncherPlatform]. /// @@ -16,7 +20,10 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { html.Window _window; // The set of schemes that can be handled by the plugin - static final _supportedSchemes = {'http', 'https', _mailtoScheme}; + static final _supportedSchemes = { + 'http', + 'https', + }.union(_safariTargetTopSchemes); /// A constructor that allows tests to override the window object used by the plugin. UrlLauncherPlugin({@visibleForTesting html.Window window}) @@ -29,16 +36,18 @@ class UrlLauncherPlugin extends UrlLauncherPlatform { String _getUrlScheme(String url) => Uri.tryParse(url)?.scheme; - bool _isMailtoScheme(String url) => _getUrlScheme(url) == _mailtoScheme; + bool _isSafariTargetTopScheme(String url) => + _safariTargetTopSchemes.contains(_getUrlScheme(url)); /// Opens the given [url] in a new window. /// /// Returns the newly created window. @visibleForTesting html.WindowBase openNewWindow(String url) { - // We need to open mailto urls on the _top window context on safari browsers. + // We need to open mailto, tel and sms urls on the _top window context on safari browsers. // See https://github.com/flutter/flutter/issues/51461 for reference. - final target = browser.isSafari && _isMailtoScheme(url) ? '_top' : ''; + final target = + browser.isSafari && _isSafariTargetTopScheme(url) ? '_top' : ''; return _window.open(url, target); } diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index 207f2dc15424..9a5beac00b94 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/u # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.1+6 +version: 0.1.2 flutter: plugin: diff --git a/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart index 4cf92062e10f..b7e107d892cf 100644 --- a/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart +++ b/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart @@ -36,8 +36,13 @@ void main() { plugin.canLaunch('mailto:name@mydomain.com'), completion(isTrue)); }); - test('"tel" URLs -> false', () { - expect(plugin.canLaunch('tel:5551234567'), completion(isFalse)); + test('"tel" URLs -> true', () { + expect(plugin.canLaunch('tel:5551234567'), completion(isTrue)); + }); + + test('"sms" URLs -> true', () { + expect(plugin.canLaunch('sms:+19725551212?body=hello%20there'), + completion(isTrue)); }); }); @@ -48,6 +53,9 @@ void main() { .thenReturn(MockWindow()); when(mockWindow.open('mailto:name@mydomain.com', '')) .thenReturn(MockWindow()); + when(mockWindow.open('tel:5551234567', '')).thenReturn(MockWindow()); + when(mockWindow.open('sms:+19725551212?body=hello%20there', '')) + .thenReturn(MockWindow()); }); test('launching a URL returns true', () { @@ -77,6 +85,34 @@ void main() { ), completion(isTrue)); }); + + test('launching a "tel" returns true', () { + expect( + plugin.launch( + 'tel:5551234567', + useSafariVC: null, + useWebView: null, + universalLinksOnly: null, + enableDomStorage: null, + enableJavaScript: null, + headers: null, + ), + completion(isTrue)); + }); + + test('launching a "sms" returns true', () { + expect( + plugin.launch( + 'sms:+19725551212?body=hello%20there', + useSafariVC: null, + useWebView: null, + universalLinksOnly: null, + enableDomStorage: null, + enableJavaScript: null, + headers: null, + ), + completion(isTrue)); + }); }); group('openNewWindow', () { @@ -98,6 +134,18 @@ void main() { verify(mockWindow.open('mailto:name@mydomain.com', '')); }); + test('tel urls should be launched on a new window', () { + plugin.openNewWindow('tel:5551234567'); + + verify(mockWindow.open('tel:5551234567', '')); + }); + + test('sms urls should be launched on a new window', () { + plugin.openNewWindow('sms:+19725551212?body=hello%20there'); + + verify(mockWindow.open('sms:+19725551212?body=hello%20there', '')); + }); + group('Safari', () { setUp(() { platform.configurePlatformForTesting(browser: platform.safari); @@ -120,6 +168,19 @@ void main() { verify(mockWindow.open('mailto:name@mydomain.com', '_top')); }); + + test('tel urls should be launched on the same window', () { + plugin.openNewWindow('tel:5551234567'); + + verify(mockWindow.open('tel:5551234567', '_top')); + }); + + test('sms urls should be launched on the same window', () { + plugin.openNewWindow('sms:+19725551212?body=hello%20there'); + + verify( + mockWindow.open('sms:+19725551212?body=hello%20there', '_top')); + }); }); }); }); diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 9928fe31c5a3..e3f49e736411 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.11+2 + +* Fix aspectRatio calculation when size.width or size.height are zero. + ## 0.10.11+1 * Post-v2 Android embedding cleanups. diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index f2f9289c1fda..a2290b2e5916 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -97,7 +97,7 @@ class VideoPlayerValue { /// Returns [size.width] / [size.height] when size is non-null, or `1.0.` when /// size is null or the aspect ratio would be less than or equal to 0.0. double get aspectRatio { - if (size == null) { + if (size == null || size.width == 0 || size.height == 0) { return 1.0; } final double aspectRatio = size.width / size.height; diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index f967cd07ef7f..03f71bf4f412 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -4,7 +4,7 @@ description: Flutter plugin for displaying inline video with other Flutter # 0.10.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.10.11+1 +version: 0.10.11+2 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index ac8459d0c9e9..ae236def4e57 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -517,6 +517,48 @@ void main() { expect(exactCopy.toString(), original.toString()); }); + + group('aspectRatio', () { + test('640x480 -> 4:3', () { + final value = VideoPlayerValue( + size: Size(640, 480), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 4 / 3); + }); + + test('null size -> 1.0', () { + final value = VideoPlayerValue( + size: null, + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + + test('height = 0 -> 1.0', () { + final value = VideoPlayerValue( + size: Size(640, 0), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + + test('width = 0 -> 1.0', () { + final value = VideoPlayerValue( + size: Size(0, 480), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + + test('negative aspect ratio -> 1.0', () { + final value = VideoPlayerValue( + size: Size(640, -480), + duration: Duration(seconds: 1), + ); + expect(value.aspectRatio, 1.0); + }); + }); }); test('VideoProgressColors', () { diff --git a/packages/video_player/video_player_web/CHANGELOG.md b/packages/video_player/video_player_web/CHANGELOG.md index cd977c335d62..9c500e951122 100644 --- a/packages/video_player/video_player_web/CHANGELOG.md +++ b/packages/video_player/video_player_web/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.1.3+2 + +* Allow users to set the 'muted' attribute on video elements by setting their volume to 0. +* Do not parse URIs on 'network' videos to not break blobs (Safari). + ## 0.1.3+1 * Remove Android folder from `video_player_web`. diff --git a/packages/video_player/video_player_web/lib/video_player_web.dart b/packages/video_player/video_player_web/lib/video_player_web.dart index 039c3ce65a7e..3a849f45e0c1 100644 --- a/packages/video_player/video_player_web/lib/video_player_web.dart +++ b/packages/video_player/video_player_web/lib/video_player_web.dart @@ -66,10 +66,12 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { final int textureId = _textureCounter; _textureCounter++; - Uri uri; + String uri; switch (dataSource.sourceType) { case DataSourceType.network: - uri = Uri.parse(dataSource.uri); + // Do NOT modify the incoming uri, it can be a Blob, and Safari doesn't + // like blobs that have changed. + uri = dataSource.uri; break; case DataSourceType.asset: String assetUrl = dataSource.asset; @@ -79,7 +81,7 @@ class VideoPlayerPlugin extends VideoPlayerPlatform { // 'webOnlyAssetManager' is only in the web version of dart:ui // ignore: undefined_prefixed_name assetUrl = ui.webOnlyAssetManager.getAssetUrl(assetUrl); - uri = Uri.parse(assetUrl); + uri = assetUrl; break; case DataSourceType.file: return Future.error(UnimplementedError( @@ -145,18 +147,21 @@ class _VideoPlayer { final StreamController eventController = StreamController(); - final Uri uri; + final String uri; final int textureId; VideoElement videoElement; bool isInitialized = false; void initialize() { videoElement = VideoElement() - ..src = uri.toString() + ..src = uri ..autoplay = false ..controls = false ..style.border = 'none'; + // Allows Safari iOS to play the video inline + videoElement.setAttribute('playsinline', 'true'); + // TODO(hterkelsen): Use initialization parameters once they are available // ignore: undefined_prefixed_name ui.platformViewRegistry.registerViewFactory( @@ -218,6 +223,12 @@ class _VideoPlayer { } void setVolume(double value) { + // TODO: Do we need to expose a "muted" API? https://github.com/flutter/flutter/issues/60721 + if (value > 0.0) { + videoElement.muted = false; + } else { + videoElement.muted = true; + } videoElement.volume = value; } diff --git a/packages/video_player/video_player_web/pubspec.yaml b/packages/video_player/video_player_web/pubspec.yaml index 523d5a79a75b..891430d7483b 100644 --- a/packages/video_player/video_player_web/pubspec.yaml +++ b/packages/video_player/video_player_web/pubspec.yaml @@ -4,7 +4,7 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/v # 0.1.y+z is compatible with 1.0.0, if you land a breaking change bump # the version to 2.0.0. # See more details: https://github.com/flutter/flutter/wiki/Package-migration-to-1.0.0 -version: 0.1.3+1 +version: 0.1.3+2 flutter: plugin: diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index 2c0a90693851..f1f68ddbe985 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -14,6 +14,7 @@ readonly EXCLUDED_PLUGINS_LIST=( "connectivity_macos" "connectivity_platform_interface" "connectivity_web" + "extension_google_sign_in_as_googleapis_auth" "flutter_plugin_android_lifecycle" "google_maps_flutter_platform_interface" "google_maps_flutter_web" @@ -26,10 +27,12 @@ readonly EXCLUDED_PLUGINS_LIST=( "path_provider_platform_interface" "path_provider_web" "plugin_platform_interface" + "shared_preferences_linux" "shared_preferences_macos" "shared_preferences_platform_interface" "shared_preferences_web" "shared_preferences_windows" + "url_launcher_linux" "url_launcher_macos" "url_launcher_platform_interface" "url_launcher_web"