From 7d7be76c6f9e4d2ab4a6014f5c876fb773d31fd3 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Tue, 12 Nov 2019 13:07:26 -0800 Subject: [PATCH 01/47] Bump google_maps_flutter pubspec to match CHANGELOG (#2267) --- packages/google_maps_flutter/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index 3e8b80283e6e..3fed14e299c0 100644 --- a/packages/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter -version: 0.5.21+9 +version: 0.5.21+10 dependencies: flutter: From 35e984fc9be311182231004b2d0829ade5122d5b Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 12 Nov 2019 16:06:38 -0800 Subject: [PATCH 02/47] [android_intent] Add missing DartDocs (#2268) Also adds a rule just for `android_intent` that makes sure that there are no undocumented public members. This will prevent the DartDocs from regressing. Also updates the main repo's analysis options to migrate from some deprecated options. If/when all the plugins have their DartDocs updated this package-specific `analysis_options.yaml` should be removed, and the DartDoc rule should be moved up to the top level. --- analysis_options.yaml | 6 +- packages/android_intent/CHANGELOG.md | 4 + packages/android_intent/analysis_options.yaml | 11 ++ packages/android_intent/example/lib/main.dart | 6 +- .../android_intent/lib/android_intent.dart | 43 ++++- packages/android_intent/lib/flag.dart | 158 +++++++++++++++++- packages/android_intent/pubspec.yaml | 2 +- 7 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 packages/android_intent/analysis_options.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml index a73c3a63e1bb..d4a06c5b9dd4 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -12,9 +12,8 @@ analyzer: language: - enableStrictCallChecks: true - enableSuperMixins: true - enableAssertInitializer: true + strict-inference: true + strict-raw-types: true strong-mode: implicit-dynamic: false errors: @@ -117,7 +116,6 @@ linter: - slash_for_doc_comments - sort_constructors_first - sort_unnamed_constructors_first - - super_goes_last # - type_annotate_public_apis # subset of always_specify_types - type_init_formals # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md index a71b5e943ffa..de7b818e0d0b 100644 --- a/packages/android_intent/CHANGELOG.md +++ b/packages/android_intent/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.4+6 + +* Add missing DartDocs for public members. + ## 0.3.4+5 * Remove AndroidX warning. diff --git a/packages/android_intent/analysis_options.yaml b/packages/android_intent/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/android_intent/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/android_intent/example/lib/main.dart b/packages/android_intent/example/lib/main.dart index f56cffd2bd20..45de9632e975 100644 --- a/packages/android_intent/example/lib/main.dart +++ b/packages/android_intent/example/lib/main.dart @@ -11,6 +11,7 @@ void main() { runApp(MyApp()); } +/// A sample app for launching intents. class MyApp extends StatelessWidget { // This widget is the root of your application. @override @@ -29,6 +30,7 @@ class MyApp extends StatelessWidget { } } +/// Holds the different intent widgets. class MyHomePage extends StatelessWidget { void _createAlarm() { final AndroidIntent intent = const AndroidIntent( @@ -80,9 +82,11 @@ class MyHomePage extends StatelessWidget { } } +/// Launches intents to specific Android activities. class ExplicitIntentsWidget extends StatelessWidget { - const ExplicitIntentsWidget(); + const ExplicitIntentsWidget(); // ignore: public_member_api_docs + // ignore: public_member_api_docs static const String routeName = "/explicitIntents"; void _openGoogleMapsStreetView() { diff --git a/packages/android_intent/lib/android_intent.dart b/packages/android_intent/lib/android_intent.dart index 9c036cf98e15..1ba8d3d15ea5 100644 --- a/packages/android_intent/lib/android_intent.dart +++ b/packages/android_intent/lib/android_intent.dart @@ -8,9 +8,13 @@ import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; import 'package:platform/platform.dart'; -const String kChannelName = 'plugins.flutter.io/android_intent'; +const String _kChannelName = 'plugins.flutter.io/android_intent'; /// Flutter plugin for launching arbitrary Android Intents. +/// +/// See [the official Android +/// documentation](https://developer.android.com/reference/android/content/Intent.html) +/// for more information on how to use Intents. class AndroidIntent { /// Builds an Android intent with the following parameters /// [action] refers to the action parameter of the intent. @@ -33,9 +37,11 @@ class AndroidIntent { this.componentName, Platform platform, }) : assert(action != null), - _channel = const MethodChannel(kChannelName), + _channel = const MethodChannel(_kChannelName), _platform = platform ?? const LocalPlatform(); + /// This constructor is only exposed for unit testing. Do not rely on this in + /// app code, it may break without warning. @visibleForTesting AndroidIntent.private({ @required this.action, @@ -50,12 +56,43 @@ class AndroidIntent { }) : _channel = channel, _platform = platform; + /// This is the general verb that the intent should attempt to do. This + /// includes constants like `ACTION_VIEW`. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. final String action; + + /// Constants that can be set on an intent to tweak how it is finally handled. + /// Some of the constants are mirrored to Dart via [Flag]. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#setFlags(int). final List flags; + + /// An optional additional constant qualifying the given [action]. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. final String category; + + /// The Uri that the [action] is pointed towards. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. final String data; + + /// The equivalent of `extras`, a generic `Bundle` of data that the Intent can + /// carry. This is a slot for extraneous data that the listener may use. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#intent-structure. final Map arguments; + + /// Sets the [data] to only resolve within this given package. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#setPackage(java.lang.String). final String package; + + /// Set the exact `ComponentName` that should handle the intent. If this is + /// set [package] should also be non-null. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#setComponent(android.content.ComponentName). final String componentName; final MethodChannel _channel; final Platform _platform; @@ -65,6 +102,8 @@ class AndroidIntent { return x != 0 && ((x & (x - 1)) == 0); } + /// This method is just visible for unit testing and should not be relied on. + /// Its method signature may change at any time. @visibleForTesting int convertFlags(List flags) { int finalValue = 0; diff --git a/packages/android_intent/lib/flag.dart b/packages/android_intent/lib/flag.dart index b4e6ed100146..e05aa6d12666 100644 --- a/packages/android_intent/lib/flag.dart +++ b/packages/android_intent/lib/flag.dart @@ -1,37 +1,193 @@ -// flag values from https://developer.android.com/reference/android/content/Intent.html +/// Special flags that can be set on an intent to control how it is handled. +/// +/// See +/// https://developer.android.com/reference/android/content/Intent.html#setFlags(int) +/// for the official documentation on Intent flags. The constants here mirror +/// the existing [android.content.Intent] ones. class Flag { + /// Specifies how an activity should be launched. Generally set by the system + /// in conjunction with SINGLE_TASK. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_BROUGHT_TO_FRONT. static const int FLAG_ACTIVITY_BROUGHT_TO_FRONT = 4194304; + + /// Causes any existing tasks associated with the activity to be cleared. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TASK static const int FLAG_ACTIVITY_CLEAR_TASK = 32768; + + /// Closes any activities on top of this activity and brings it to the front, + /// if it's currently running. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP static const int FLAG_ACTIVITY_CLEAR_TOP = 67108864; + + /// @deprecated Use [FLAG_ACTIVITY_NEW_DOCUMENT] instead when on API 21 or above. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET + @deprecated static const int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 524288; + + /// Keeps the activity from being listed with other recently launched + /// activities. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS static const int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; + + /// Forwards the result from this activity to the existing one. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_FORWARD_RESULT static const int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; + + /// Generally set by the system if the activity is being launched from + /// history. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY static const int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; + + /// Used in split-screen mode to set the launched activity adjacent to the + /// launcher. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_LAUNCH_ADJACENT static const int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; + + /// Used in split-screen mode to set the launched activity adjacent to the + /// launcher. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_MATCH_EXTERNAL static const int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; + + /// Creates and launches the activity into a new task. Should always be + /// combined with [FLAG_ACTIVITY_NEW_DOCUMENT] or [FLAG_ACTIVITY_NEW_TASK]. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_MULTIPLE_TASK. static const int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; + + /// Opens a document into a new task rooted in this activity. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_DOCUMENT. static const int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; + + /// The launched activity starts a new task on the activity stack. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK. static const int FLAG_ACTIVITY_NEW_TASK = 268435456; + + /// Prevents the system from playing an activity transition animation when + /// launching this. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NO_ANIMATION. static const int FLAG_ACTIVITY_NO_ANIMATION = 65536; + + /// Does not keep the launched activity in history. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NO_HISTORY. static const int FLAG_ACTIVITY_NO_HISTORY = 1073741824; + + /// Prevents a typical callback from occuring when the activity is paused. + /// + /// https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NO_USER_ACTION static const int FLAG_ACTIVITY_NO_USER_ACTION = 262144; + + /// Uses the previous activity as top when applicable. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_PREVIOUS_IS_TOP. static const int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 16777216; + + /// Brings any already instances of this activity to the front. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_REORDER_TO_FRONT. static const int FLAG_ACTIVITY_REORDER_TO_FRONT = 131072; + + /// Launches the activity in a way that resets the task in some cases. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_RESET_TASK_IF_NEEDED. static const int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 2097152; + + /// Keeps an entry in recent tasks. Used with [FLAG_ACTIVITY_NEW_DOCUMENT]. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_RETAIN_IN_RECENTS. static const int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 8192; + + /// Will not re-launch the activity if it is already at the top of the history + /// stack. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_SINGLE_TOP. static const int FLAG_ACTIVITY_SINGLE_TOP = 536870912; + + /// Places the activity on top of the home task. Must be used with + /// [FLAG_ACTIVITY_NEW_TASK]. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_TASK_ON_HOME. static const int FLAG_ACTIVITY_TASK_ON_HOME = 16384; + + /// Prints debug logs while the intent is resolving. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_DEBUG_LOG_RESOLUTION. static const int FLAG_DEBUG_LOG_RESOLUTION = 8; + + /// Does not match to any stopped components. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_EXCLUDE_STOPPED_PACKAGES. static const int FLAG_EXCLUDE_STOPPED_PACKAGES = 16; + + /// Can be set by the caller to flag the intent as not being launched directly + /// by the user. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_FROM_BACKGROUND. static const int FLAG_FROM_BACKGROUND = 4; + + /// Will persist the URI permision across device reboots. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_GRANT_PERSISTABLE_URI_PERMISSION. static const int FLAG_GRANT_PERSISTABLE_URI_PERMISSION = 64; + + /// Applies the URI permission grant based on prefix matching. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_GRANT_PREFIX_URI_PERMISSION. static const int FLAG_GRANT_PREFIX_URI_PERMISSION = 128; + + /// Grants the intent listener permission to read extra data from the Intent's + /// URI. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_GRANT_READ_URI_PERMISSION. static const int FLAG_GRANT_READ_URI_PERMISSION = 1; + + /// Grants the intent listener permission to write extra data from the + /// Intent's URI. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_GRANT_WRITE_URI_PERMISSION. static const int FLAG_GRANT_WRITE_URI_PERMISSION = 2; + + /// Always matches stopped components. This is the default behavior. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_INCLUDE_STOPPED_PACKAGES. static const int FLAG_INCLUDE_STOPPED_PACKAGES = 32; + + /// Allows the listener to run at a high priority. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_RECEIVER_FOREGROUND. static const int FLAG_RECEIVER_FOREGROUND = 268435456; + + /// Doesn't allow listeners to cancel the broadcast. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_RECEIVER_NO_ABORT. static const int FLAG_RECEIVER_NO_ABORT = 134217728; + + /// Only allows registered receivers to listen for the intent. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_RECEIVER_REGISTERED_ONLY. static const int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; + + /// Will drop any pending broadcasts of this intent in favor of the newest + /// one. + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_RECEIVER_REPLACE_PENDING. static const int FLAG_RECEIVER_REPLACE_PENDING = 536870912; + + /// Instant Apps will be able to listen for the intent (not the default + /// behavior). + /// + /// See https://developer.android.com/reference/android/content/Intent.html#FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS. static const int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 2097152; } diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml index b0248ad2a5cd..3048d5bbbdf2 100644 --- a/packages/android_intent/pubspec.yaml +++ b/packages/android_intent/pubspec.yaml @@ -2,7 +2,7 @@ name: android_intent description: Flutter plugin for launching Android Intents. Not supported on iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent -version: 0.3.4+5 +version: 0.3.4+6 flutter: plugin: From bbee85f2d22a83394338ae5469861fab06ff4e81 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 12 Nov 2019 16:47:27 -0800 Subject: [PATCH 03/47] [infra] Ignore analyzer issues in CI (#2271) 35e984fc migrated some deprecated analyzer warnings to the newer alternatives. These created some failures in unrelated plugins based on the warnings being slightly different. Added `ignore`s for now to turn CI green again as soon as possible. The previous analysis options really should not be used and the majority of the patch was unrelated, so I prefer ignoring specific already failing cases to reverting in this case. Including an `analysis_options.yaml` file with deprecated analyzer rules causes analyzer failures in the including package, so that patch can't be landed in a way that prevents regressions without updated rules. --- packages/android_alarm_manager/lib/android_alarm_manager.dart | 4 ++++ packages/camera/lib/camera.dart | 1 + .../lib/src/in_app_purchase/google_play_connection.dart | 2 ++ 3 files changed, 7 insertions(+) diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index b90823e5c9d4..76994b9edac7 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -39,8 +39,10 @@ void _alarmManagerCallbackDispatcher() { exit(-1); } + // ignore: inference_failure_on_function_return_type if (closure is Function()) { closure(); + // ignore: inference_failure_on_function_return_type } else if (closure is Function(int)) { final int id = args[1]; closure(id); @@ -182,6 +184,7 @@ class AndroidAlarmManager { bool wakeup = false, bool rescheduleOnReboot = false, }) async { + // ignore: inference_failure_on_function_return_type assert(callback is Function() || callback is Function(int)); assert(id.bitLength < 32); final int startMillis = time.millisecondsSinceEpoch; @@ -245,6 +248,7 @@ class AndroidAlarmManager { bool wakeup = false, bool rescheduleOnReboot = false, }) async { + // ignore: inference_failure_on_function_return_type assert(callback is Function() || callback is Function(int)); assert(id.bitLength < 32); final int now = DateTime.now().millisecondsSinceEpoch; diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index ee1892c4cbc0..cc12b907fa65 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -38,6 +38,7 @@ enum ResolutionPreset { max, } +// ignore: inference_failure_on_function_return_type typedef onLatestImageAvailable = Function(CameraImage image); /// Returns the resolution preset as a String. diff --git a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart index 32f8fc79681e..d64467ed3775 100644 --- a/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart +++ b/packages/in_app_purchase/lib/src/in_app_purchase/google_play_connection.dart @@ -186,8 +186,10 @@ class GooglePlayConnection } on PlatformException catch (e) { exception = e; responses = [ + // ignore: invalid_use_of_visible_for_testing_member SkuDetailsResponseWrapper( responseCode: BillingResponse.error, skuDetailsList: []), + // ignore: invalid_use_of_visible_for_testing_member SkuDetailsResponseWrapper( responseCode: BillingResponse.error, skuDetailsList: []) ]; From 76854d68e840ae2125b862d6b142e45405af2769 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Tue, 12 Nov 2019 17:06:09 -0800 Subject: [PATCH 04/47] [quick_actions] Update and migrate iOS example project (#2100) --- packages/quick_actions/CHANGELOG.md | 6 ++ .../ios/Runner.xcodeproj/project.pbxproj | 60 ++++++++----------- .../xcshareddata/xcschemes/Runner.xcscheme | 10 +--- packages/quick_actions/pubspec.yaml | 2 +- 4 files changed, 35 insertions(+), 43 deletions(-) diff --git a/packages/quick_actions/CHANGELOG.md b/packages/quick_actions/CHANGELOG.md index c5d442932f6d..ca7b674e77b5 100644 --- a/packages/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.3.3+1 + +* Update and migrate iOS example project by removing flutter_assets, change + "English" to "en", remove extraneous xcconfigs, update to Xcode 11 build + settings, and remove ARCHS and DEVELOPMENT_TEAM. + ## 0.3.3 * Support Android V2 embedding. diff --git a/packages/quick_actions/example/ios/Runner.xcodeproj/project.pbxproj b/packages/quick_actions/example/ios/Runner.xcodeproj/project.pbxproj index 9d6dc0f30f21..fdd275fcede5 100644 --- a/packages/quick_actions/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/quick_actions/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,15 +8,12 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 83C36CAF23D629E5ABE75B2A /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CCC799F2B0AB50A9C34344F0 /* libPods-Runner.a */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 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 */; }; @@ -42,9 +39,9 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 5278439583922091276A37C9 /* 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 = ""; }; 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 = ""; }; @@ -58,6 +55,7 @@ 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 = ""; }; CCC799F2B0AB50A9C34344F0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + F0609304FBCAEC2289164BD5 /* 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 */ @@ -79,7 +77,6 @@ children = ( 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -142,6 +139,8 @@ D0FE95BE2380323DD75CB891 /* Pods */ = { isa = PBXGroup; children = ( + 5278439583922091276A37C9 /* Pods-Runner.debug.xcconfig */, + F0609304FBCAEC2289164BD5 /* Pods-Runner.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -161,7 +160,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, FEDDF02AA7C2BA0D1905BD95 /* [CP] Embed Pods Frameworks */, - BEA76C3BEB02665DE83A6355 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -178,18 +176,17 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = JSJA5AH6K6; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -211,10 +208,7 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -251,21 +245,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - BEA76C3BEB02665DE83A6355 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; C6989ECD8FF0836301D734B4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -290,16 +269,13 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -342,19 +318,28 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 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; @@ -390,19 +375,28 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 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; @@ -431,9 +425,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = JSJA5AH6K6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -454,9 +446,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = JSJA5AH6K6; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/quick_actions/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/quick_actions/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c9580788197..3bb3697ef41c 100644 --- a/packages/quick_actions/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/quick_actions/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - homepage: https://github.com/flutter/plugins/tree/master/packages/quick_actions -version: 0.3.3 +version: 0.3.3+1 flutter: plugin: From e87ef4ce248491b8879714278701b7efb4b85119 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Tue, 12 Nov 2019 17:11:00 -0800 Subject: [PATCH 05/47] [shared_preferences] Update and migrate iOS example project (#2103) --- packages/shared_preferences/CHANGELOG.md | 6 ++ .../ios/Runner.xcodeproj/project.pbxproj | 57 ++++++++----------- .../xcshareddata/xcschemes/Runner.xcscheme | 10 +--- packages/shared_preferences/pubspec.yaml | 2 +- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/packages/shared_preferences/CHANGELOG.md b/packages/shared_preferences/CHANGELOG.md index 5560dd8a4317..1cc5c511c8d2 100644 --- a/packages/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.5.4+5 + +* Update and migrate iOS example project by removing flutter_assets, change + "English" to "en", remove extraneous xcconfigs and framework outputs, + update to Xcode 11 build settings, and remove ARCHS. + ## 0.5.4+4 * `setMockInitialValues` needs to handle non-prefixed keys since that's an implementation detail. diff --git a/packages/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj b/packages/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj index 6b6940129013..2adc7021c6bf 100644 --- a/packages/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 2D92224B1EC342E7007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D92224A1EC342E7007564B0 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; @@ -15,8 +14,6 @@ 66F8BCECCEFF62F4071D2DFC /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E03CD14DABAA3AD3923183 /* libPods-Runner.a */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 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 */; }; @@ -40,8 +37,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 081A3238A89B77A99B096D83 /* 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 = ""; }; 12E03CD14DABAA3AD3923183 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 2D9222491EC342E7007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 2D92224A1EC342E7007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; @@ -49,6 +46,7 @@ 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 = ""; }; + 942E815CEF30E101E045B849 /* 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 = ""; }; 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 = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; @@ -77,6 +75,8 @@ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( + 942E815CEF30E101E045B849 /* Pods-Runner.debug.xcconfig */, + 081A3238A89B77A99B096D83 /* Pods-Runner.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -86,7 +86,6 @@ children = ( 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -160,7 +159,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */, - 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( @@ -178,7 +176,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -188,7 +186,7 @@ }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -210,10 +208,7 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -236,37 +231,19 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; }; - 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -341,19 +318,28 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 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; @@ -389,19 +375,28 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 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; @@ -430,7 +425,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -452,7 +446,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/packages/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c9580788197..3bb3697ef41c 100644 --- a/packages/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences -version: 0.5.4+4 +version: 0.5.4+5 flutter: plugin: From 3c59297c1982a7694356c7e35ad49b2b2515e3b7 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Wed, 13 Nov 2019 09:25:48 -0800 Subject: [PATCH 06/47] [connectivity] Lint for public DartDocs (#2269) Missing DartDocs were recently added to this plugin. Add a package-specific analysis options to make sure that they don't regress going forward. --- packages/connectivity/CHANGELOG.md | 4 ++++ packages/connectivity/analysis_options.yaml | 11 +++++++++++ packages/connectivity/example/lib/main.dart | 2 ++ packages/connectivity/lib/connectivity.dart | 17 +++++++++++------ packages/connectivity/pubspec.yaml | 2 +- 5 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 packages/connectivity/analysis_options.yaml diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index 3ede2383d207..17e49e25cbd8 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.5+5 + +* Add an analyzer check for the public documentation. + ## 0.4.5+4 * Stability and Maintainability: update documentations. diff --git a/packages/connectivity/analysis_options.yaml b/packages/connectivity/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/connectivity/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/connectivity/example/lib/main.dart b/packages/connectivity/example/lib/main.dart index c01a110efb60..c67e90881e02 100644 --- a/packages/connectivity/example/lib/main.dart +++ b/packages/connectivity/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 'dart:io'; diff --git a/packages/connectivity/lib/connectivity.dart b/packages/connectivity/lib/connectivity.dart index 8108b5c52a79..ad9fae317012 100644 --- a/packages/connectivity/lib/connectivity.dart +++ b/packages/connectivity/lib/connectivity.dart @@ -8,12 +8,17 @@ import 'dart:io'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; -/// Connection Status Check Result -/// -/// WiFi: Device connected via Wi-Fi -/// Mobile: Device connected to cellular network -/// None: Device not connected to any network -enum ConnectivityResult { wifi, mobile, none } +/// Connection status check result. +enum ConnectivityResult { + /// WiFi: Device connected via Wi-Fi + wifi, + + /// Mobile: Device connected to cellular network + mobile, + + /// None: Device not connected to any network + none +} /// Discover network connectivity configurations: Distinguish between WI-FI and cellular, check WI-FI status and more. class Connectivity { diff --git a/packages/connectivity/pubspec.yaml b/packages/connectivity/pubspec.yaml index ede6c241c836..c68109d835da 100644 --- a/packages/connectivity/pubspec.yaml +++ b/packages/connectivity/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity -version: 0.4.5+4 +version: 0.4.5+5 flutter: plugin: From f5f0d570a6f066a91ed754a4a4b4ed3acfbe0419 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Wed, 13 Nov 2019 09:27:13 -0800 Subject: [PATCH 07/47] [image_picker] Lint for public DartDocs (#2270) Missing DartDocs were recently added to this plugin. Add a package-specific analysis options to make sure that they don't regress going forward. --- packages/image_picker/analysis_options.yaml | 11 +++++++++++ packages/image_picker/example/lib/main.dart | 2 ++ packages/image_picker/lib/image_picker.dart | 20 ++++++++++++++++---- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 packages/image_picker/analysis_options.yaml diff --git a/packages/image_picker/analysis_options.yaml b/packages/image_picker/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/image_picker/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/image_picker/example/lib/main.dart b/packages/image_picker/example/lib/main.dart index a2175c098f17..b728788f5ba7 100755 --- a/packages/image_picker/example/lib/main.dart +++ b/packages/image_picker/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 'dart:io'; diff --git a/packages/image_picker/lib/image_picker.dart b/packages/image_picker/lib/image_picker.dart index b1a7c8b36c00..19cd0752ced3 100755 --- a/packages/image_picker/lib/image_picker.dart +++ b/packages/image_picker/lib/image_picker.dart @@ -8,7 +8,10 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +/// Denotes that an image is being picked. const String kTypeImage = 'image'; + +/// Denotes that a video is being picked. const String kTypeVideo = 'video'; /// Specifies the source where the picked image should come from. @@ -143,14 +146,17 @@ class ImagePicker { /// See also: /// * [ImagePicker.retrieveLostData] for more details on retrieving lost data. class LostDataResponse { + /// Creates an instance with the given [file], [exception], and [type]. Any of + /// the params may be null, but this is never considered to be empty. LostDataResponse({this.file, this.exception, this.type}); + /// Initializes an instance with all member params set to null and considered + /// to be empty. LostDataResponse.empty() : file = null, exception = null, - type = null { - _empty = true; - } + type = null, + _empty = true; /// Whether it is an empty response. /// @@ -178,4 +184,10 @@ class LostDataResponse { } /// The type of the retrieved data in a [LostDataResponse]. -enum RetrieveType { image, video } +enum RetrieveType { + /// A static picture. See [ImagePicker.pickImage]. + image, + + /// A video. See [ImagePicker.pickVideo]. + video +} From 218016f88117b46d65e4c3c93c302475f62a1258 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Wed, 13 Nov 2019 09:28:31 -0800 Subject: [PATCH 08/47] [sensors] Documentation and test improvements (#2272) - Add missing dartdoc coverage and expand on existing documentation. - Add an analyzer warning for this package so that public doc coverage doesn't regress. - Improve unit test coverage. - Expand on the existing README. --- packages/sensors/CHANGELOG.md | 4 ++ packages/sensors/README.md | 34 ++++++++-- packages/sensors/analysis_options.yaml | 11 +++ packages/sensors/example/lib/main.dart | 2 + packages/sensors/example/lib/snake.dart | 2 + packages/sensors/lib/sensors.dart | 47 +++++++++++++ packages/sensors/pubspec.yaml | 3 +- packages/sensors/test/sensors_test.dart | 90 +++++++++++++++---------- 8 files changed, 154 insertions(+), 39 deletions(-) create mode 100644 packages/sensors/analysis_options.yaml diff --git a/packages/sensors/CHANGELOG.md b/packages/sensors/CHANGELOG.md index ea488586cb87..aecf8d4c83ec 100644 --- a/packages/sensors/CHANGELOG.md +++ b/packages/sensors/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.1+3 + +* Improve documentation and add unit test coverage. + ## 0.4.1+2 * Remove AndroidX warnings. diff --git a/packages/sensors/README.md b/packages/sensors/README.md index fdd450217487..4bb9f4f254e1 100644 --- a/packages/sensors/README.md +++ b/packages/sensors/README.md @@ -5,7 +5,22 @@ A Flutter plugin to access the accelerometer and gyroscope sensors. ## Usage -To use this plugin, add `sensors` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). +To use this plugin, add `sensors` as a [dependency in your pubspec.yaml +file](https://flutter.io/platform-plugins/). + +This will expose three classes of sensor events, through three different +streams. + +- `AccelerometerEvent`s describe the velocity of the device, including the + effects of gravity. Put simply, you can use accelerometer readings to tell if + the device is moving in a particular direction. +- `UserAccelerometerEvent`s also describe the velocity of the device, but don't + include gravity. They can also be thought of as just the user's affect on the + device. +- `GyroscopeEvent`s describe the rotation of the device. + +Each of these is exposed through a `BroadcastStream`: `accelerometerEvents`, +`userAccelerometerEvents`, and `gyroscopeEvents`, respectively. ### Example @@ -14,10 +29,21 @@ To use this plugin, add `sensors` as a [dependency in your pubspec.yaml file](ht import 'package:sensors/sensors.dart'; accelerometerEvents.listen((AccelerometerEvent event) { - // Do something with the event. + print(event); }); +// [AccelerometerEvent (x: 0.0, y: 9.8, z: 0.0)] + +userAccelerometerEvents.listen((AccelerometerEvent event) { + print(event); +}); +// [UserAccelerometerEvent (x: 0.0, y: 0.0, z: 0.0)] gyroscopeEvents.listen((GyroscopeEvent event) { - // Do something with the event. + print(event); }); -``` \ No newline at end of file +// [GyroscopeEvent (x: 0.0, y: 0.0, z: 0.0)] + +``` + +Also see the `example` subdirectory for an example application that uses the +sensor data. diff --git a/packages/sensors/analysis_options.yaml b/packages/sensors/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/sensors/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/sensors/example/lib/main.dart b/packages/sensors/example/lib/main.dart index e574a64f5f38..575e0493742f 100644 --- a/packages/sensors/example/lib/main.dart +++ b/packages/sensors/example/lib/main.dart @@ -2,6 +2,8 @@ // 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:sensors/sensors.dart'; diff --git a/packages/sensors/example/lib/snake.dart b/packages/sensors/example/lib/snake.dart index b870791618e9..2b7cad4cf2e0 100644 --- a/packages/sensors/example/lib/snake.dart +++ b/packages/sensors/example/lib/snake.dart @@ -2,6 +2,8 @@ // 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 'dart:math' as math; diff --git a/packages/sensors/lib/sensors.dart b/packages/sensors/lib/sensors.dart index 7b41a4959f94..0b6f1b5a6067 100644 --- a/packages/sensors/lib/sensors.dart +++ b/packages/sensors/lib/sensors.dart @@ -14,48 +14,95 @@ const EventChannel _userAccelerometerEventChannel = const EventChannel _gyroscopeEventChannel = EventChannel('plugins.flutter.io/sensors/gyroscope'); +/// Discrete reading from an accelerometer. Accelerometers measure the velocity +/// of the device. Note that these readings include the effects of gravity. Put +/// simply, you can use accelerometer readings to tell if the device is moving in +/// a particular direction. class AccelerometerEvent { + /// Contructs an instance with the given [x], [y], and [z] values. AccelerometerEvent(this.x, this.y, this.z); /// Acceleration force along the x axis (including gravity) measured in m/s^2. + /// + /// When the device is held upright facing the user, positive values mean the + /// device is moving to the right and negative mean it is moving to the left. final double x; /// Acceleration force along the y axis (including gravity) measured in m/s^2. + /// + /// When the device is held upright facing the user, positive values mean the + /// device is moving towards the sky and negative mean it is moving towards + /// the ground. final double y; /// Acceleration force along the z axis (including gravity) measured in m/s^2. + /// + /// This uses a right-handed coordinate system. So when the device is held + /// upright and facing the user, positive values mean the device is moving + /// towards the user and negative mean it is moving away from them. final double z; @override String toString() => '[AccelerometerEvent (x: $x, y: $y, z: $z)]'; } +/// Discrete reading from a gyroscope. Gyroscopes measure the rate or rotation of +/// the device in 3D space. class GyroscopeEvent { + /// Contructs an instance with the given [x], [y], and [z] values. GyroscopeEvent(this.x, this.y, this.z); /// Rate of rotation around the x axis measured in rad/s. + /// + /// When the device is held upright, this can also be thought of as describing + /// "pitch". The top of the device will tilt towards or away from the + /// user as this value changes. final double x; /// Rate of rotation around the y axis measured in rad/s. + /// + /// When the device is held upright, this can also be thought of as describing + /// "yaw". The lengthwise edge of the device will rotate towards or away from + /// the user as this value changes. final double y; /// Rate of rotation around the z axis measured in rad/s. + /// + /// When the device is held upright, this can also be thought of as describing + /// "roll". When this changes the face of the device should remain facing + /// forward, but the orientation will change from portrait to landscape and so + /// on. final double z; @override String toString() => '[GyroscopeEvent (x: $x, y: $y, z: $z)]'; } +/// Like [AccelerometerEvent], this is a discrete reading from an accelerometer +/// and measures the velocity of the device. However, unlike +/// [AccelerometerEvent], this event does not include the effects of gravity. class UserAccelerometerEvent { + /// Contructs an instance with the given [x], [y], and [z] values. UserAccelerometerEvent(this.x, this.y, this.z); /// Acceleration force along the x axis (excluding gravity) measured in m/s^2. + /// + /// When the device is held upright facing the user, positive values mean the + /// device is moving to the right and negative mean it is moving to the left. final double x; /// Acceleration force along the y axis (excluding gravity) measured in m/s^2. + /// + /// When the device is held upright facing the user, positive values mean the + /// device is moving towards the sky and negative mean it is moving towards + /// the ground. final double y; /// Acceleration force along the z axis (excluding gravity) measured in m/s^2. + /// + /// This uses a right-handed coordinate system. So when the device is held + /// upright and facing the user, positive values mean the device is moving + /// towards the user and negative mean it is moving away from them. final double z; @override diff --git a/packages/sensors/pubspec.yaml b/packages/sensors/pubspec.yaml index 38e698299b9e..b750d93865c6 100644 --- a/packages/sensors/pubspec.yaml +++ b/packages/sensors/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for accessing the Android and iOS accelerometer and gyroscope sensors. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/sensors -version: 0.4.1+2 +version: 0.4.1+3 flutter: plugin: @@ -20,6 +20,7 @@ dev_dependencies: flutter_test: sdk: flutter e2e: ^0.2.0 + mockito: ^4.1.1 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" diff --git a/packages/sensors/test/sensors_test.dart b/packages/sensors/test/sensors_test.dart index 603f805386fd..1485d589ad72 100644 --- a/packages/sensors/test/sensors_test.dart +++ b/packages/sensors/test/sensors_test.dart @@ -2,7 +2,6 @@ // 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:typed_data'; import 'package:flutter/services.dart'; @@ -16,44 +15,67 @@ void main() { test('$accelerometerEvents are streamed', () async { const String channelName = 'plugins.flutter.io/sensors/accelerometer'; const List sensorData = [1.0, 2.0, 3.0]; + _initializeFakeSensorChannel(channelName, sensorData); - const StandardMethodCodec standardMethod = StandardMethodCodec(); - - void emitEvent(ByteData event) { - // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. - // https://github.com/flutter/flutter/issues/33446 - // ignore: deprecated_member_use - BinaryMessages.handlePlatformMessage( - channelName, - event, - (ByteData reply) {}, - ); - } + final AccelerometerEvent event = await accelerometerEvents.first; + + expect(event.x, sensorData[0]); + expect(event.y, sensorData[1]); + expect(event.z, sensorData[2]); + }); + + test('$gyroscopeEvents are streamed', () async { + const String channelName = 'plugins.flutter.io/sensors/gyroscope'; + const List sensorData = [3.0, 4.0, 5.0]; + _initializeFakeSensorChannel(channelName, sensorData); + + final GyroscopeEvent event = await gyroscopeEvents.first; + + expect(event.x, sensorData[0]); + expect(event.y, sensorData[1]); + expect(event.z, sensorData[2]); + }); + + test('$userAccelerometerEvents are streamed', () async { + const String channelName = 'plugins.flutter.io/sensors/user_accel'; + const List sensorData = [6.0, 7.0, 8.0]; + _initializeFakeSensorChannel(channelName, sensorData); - bool isCanceled = false; + final UserAccelerometerEvent event = await userAccelerometerEvents.first; + + expect(event.x, sensorData[0]); + expect(event.y, sensorData[1]); + expect(event.z, sensorData[2]); + }); +} + +void _initializeFakeSensorChannel(String channelName, List sensorData) { + const StandardMethodCodec standardMethod = StandardMethodCodec(); + + void _emitEvent(ByteData event) { // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. // https://github.com/flutter/flutter/issues/33446 // ignore: deprecated_member_use - BinaryMessages.setMockMessageHandler(channelName, (ByteData message) async { - final MethodCall methodCall = standardMethod.decodeMethodCall(message); - if (methodCall.method == 'listen') { - emitEvent(standardMethod.encodeSuccessEnvelope(sensorData)); - emitEvent(null); - return standardMethod.encodeSuccessEnvelope(null); - } else if (methodCall.method == 'cancel') { - isCanceled = true; - return standardMethod.encodeSuccessEnvelope(null); - } else { - fail('Expected listen or cancel'); - } - }); - - final AccelerometerEvent event = await accelerometerEvents.first; - expect(event.x, 1.0); - expect(event.y, 2.0); - expect(event.z, 3.0); + BinaryMessages.handlePlatformMessage( + channelName, + event, + (ByteData reply) {}, + ); + } - await Future.delayed(Duration.zero); - expect(isCanceled, isTrue); + // TODO(hterkelsen): Remove this when defaultBinaryMessages is in stable. + // https://github.com/flutter/flutter/issues/33446 + // ignore: deprecated_member_use + BinaryMessages.setMockMessageHandler(channelName, (ByteData message) async { + final MethodCall methodCall = standardMethod.decodeMethodCall(message); + if (methodCall.method == 'listen') { + _emitEvent(standardMethod.encodeSuccessEnvelope(sensorData)); + _emitEvent(null); + return standardMethod.encodeSuccessEnvelope(null); + } else if (methodCall.method == 'cancel') { + return standardMethod.encodeSuccessEnvelope(null); + } else { + fail('Expected listen or cancel'); + } }); } From 9ddc6c1641530b7f98ce5a20d7bc5b4e69fed8fd Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Wed, 13 Nov 2019 10:59:21 -0800 Subject: [PATCH 09/47] [path_provider] Update and migrate iOS example project (#2099) --- packages/path_provider/CHANGELOG.md | 6 ++ .../ios/Runner.xcodeproj/project.pbxproj | 67 +++++++++---------- .../xcshareddata/xcschemes/Runner.xcscheme | 10 +-- packages/path_provider/pubspec.yaml | 2 +- 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md index 4c4dca44220e..92ee15cbd918 100644 --- a/packages/path_provider/CHANGELOG.md +++ b/packages/path_provider/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.4.2 + +* Update and migrate iOS example project by removing flutter_assets, change + "English" to "en", remove extraneous xcconfigs, update to Xcode 11 build + settings, remove ARCHS, and build pods as libraries instead of frameworks. + ## 1.4.1 * Remove AndroidX warnings. diff --git a/packages/path_provider/example/ios/Runner.xcodeproj/project.pbxproj b/packages/path_provider/example/ios/Runner.xcodeproj/project.pbxproj index 43d34117f5fd..eb0222a7c9c5 100644 --- a/packages/path_provider/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/path_provider/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,16 +7,13 @@ objects = { /* Begin PBXBuildFile section */ - 0675671949C15323862C164B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06B796751C0435964931B69B /* Pods_Runner.framework */; }; 2D9222481EC32A19007564B0 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D9222471EC32A19007564B0 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 85DDFCF6BBDEE02B9D9F8138 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 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 */; }; @@ -40,12 +37,11 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 06B796751C0435964931B69B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2D9222461EC32A19007564B0 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 2D9222471EC32A19007564B0 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 694A199F61914F41AAFD0B7F /* 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 = ""; }; 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 = ""; }; @@ -58,6 +54,8 @@ 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 = ""; }; + C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D317CA1E83064E01753D8BB5 /* 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 */ @@ -67,7 +65,7 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - 0675671949C15323862C164B /* Pods_Runner.framework in Frameworks */, + 85DDFCF6BBDEE02B9D9F8138 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -77,6 +75,8 @@ 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { isa = PBXGroup; children = ( + 694A199F61914F41AAFD0B7F /* Pods-Runner.debug.xcconfig */, + D317CA1E83064E01753D8BB5 /* Pods-Runner.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -86,7 +86,6 @@ children = ( 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -141,7 +140,7 @@ CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { isa = PBXGroup; children = ( - 06B796751C0435964931B69B /* Pods_Runner.framework */, + C0EE60090AA5F3AAAF2175B6 /* libPods-Runner.a */, ); name = Frameworks; sourceTree = ""; @@ -160,7 +159,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */, - 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( @@ -178,7 +176,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -188,7 +186,7 @@ }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -210,10 +208,7 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -236,21 +231,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; }; - 532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -263,7 +243,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -286,13 +266,16 @@ 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_ROOT}/../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"; + 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 */ @@ -335,19 +318,28 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 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; @@ -383,19 +375,28 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 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; @@ -424,7 +425,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -446,7 +446,6 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/packages/path_provider/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/path_provider/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c9580788197..3bb3697ef41c 100644 --- a/packages/path_provider/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/path_provider/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider -version: 1.4.1 +version: 1.4.2 flutter: plugin: From a336b28735a9d80dbc59bf81590a594117af71f4 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Wed, 13 Nov 2019 12:12:04 -0800 Subject: [PATCH 10/47] [video_player] Add v2 embedding support (#2226) - Adds support in the plugin - Adds a v2 embedding to the example app - Fixes a broken remote example in the example app - Increments the Flutter SDK dependency - Increments the version - Adds e2e tests for some simple use cases of the plugin --- packages/video_player/CHANGELOG.md | 5 + packages/video_player/android/build.gradle | 26 ++ .../plugins/videoplayer/VideoPlayer.java | 283 +++++++++++++ .../videoplayer/VideoPlayerPlugin.java | 392 ++++-------------- .../android/app/src/main/AndroidManifest.xml | 54 ++- .../EmbeddingV1Activity.java | 17 + .../videoplayerexample/MainActivity.java | 11 +- .../example/android/gradle.properties | 3 + packages/video_player/example/lib/main.dart | 4 +- packages/video_player/example/pubspec.yaml | 4 +- .../example/test_driver/video_player_e2e.dart | 67 +++ .../test_driver/video_player_e2e_test.dart | 16 + packages/video_player/pubspec.yaml | 4 +- 13 files changed, 550 insertions(+), 336 deletions(-) create mode 100644 packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java create mode 100644 packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java create mode 100644 packages/video_player/example/test_driver/video_player_e2e.dart create mode 100644 packages/video_player/example/test_driver/video_player_e2e_test.dart diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index d4882496320f..759c955e39df 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.10.3 + +* Add support for the v2 Android embedding. This shouldn't impact existing + functionality. + ## 0.10.2+6 * Remove AndroidX warnings. diff --git a/packages/video_player/android/build.gradle b/packages/video_player/android/build.gradle index edbb4c7acce4..4a73ec52e0ad 100644 --- a/packages/video_player/android/build.gradle +++ b/packages/video_player/android/build.gradle @@ -45,3 +45,29 @@ android { implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.9.6' } } + +// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java new file mode 100644 index 000000000000..43123ef09238 --- /dev/null +++ b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -0,0 +1,283 @@ +package io.flutter.plugins.videoplayer; + +import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; +import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.view.Surface; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.ExoPlaybackException; +import com.google.android.exoplayer2.ExoPlayerFactory; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.Player.EventListener; +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; +import com.google.android.exoplayer2.source.ExtractorMediaSource; +import com.google.android.exoplayer2.source.MediaSource; +import com.google.android.exoplayer2.source.dash.DashMediaSource; +import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; +import com.google.android.exoplayer2.source.hls.HlsMediaSource; +import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelector; +import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.util.Util; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.view.TextureRegistry; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final class VideoPlayer { + private static final String FORMAT_SS = "ss"; + private static final String FORMAT_DASH = "dash"; + private static final String FORMAT_HLS = "hls"; + private static final String FORMAT_OTHER = "other"; + + private SimpleExoPlayer exoPlayer; + + private Surface surface; + + private final TextureRegistry.SurfaceTextureEntry textureEntry; + + private QueuingEventSink eventSink = new QueuingEventSink(); + + private final EventChannel eventChannel; + + private boolean isInitialized = false; + + VideoPlayer( + Context context, + EventChannel eventChannel, + TextureRegistry.SurfaceTextureEntry textureEntry, + String dataSource, + Result result, + String formatHint) { + this.eventChannel = eventChannel; + this.textureEntry = textureEntry; + + TrackSelector trackSelector = new DefaultTrackSelector(); + exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector); + + Uri uri = Uri.parse(dataSource); + + DataSource.Factory dataSourceFactory; + if (isHTTP(uri)) { + dataSourceFactory = + new DefaultHttpDataSourceFactory( + "ExoPlayer", + null, + DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, + true); + } else { + dataSourceFactory = new DefaultDataSourceFactory(context, "ExoPlayer"); + } + + MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context); + exoPlayer.prepare(mediaSource); + + setupVideoPlayer(eventChannel, textureEntry, result); + } + + private static boolean isHTTP(Uri uri) { + if (uri == null || uri.getScheme() == null) { + return false; + } + String scheme = uri.getScheme(); + return scheme.equals("http") || scheme.equals("https"); + } + + private MediaSource buildMediaSource( + Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) { + int type; + if (formatHint == null) { + type = Util.inferContentType(uri.getLastPathSegment()); + } else { + switch (formatHint) { + case FORMAT_SS: + type = C.TYPE_SS; + break; + case FORMAT_DASH: + type = C.TYPE_DASH; + break; + case FORMAT_HLS: + type = C.TYPE_HLS; + break; + case FORMAT_OTHER: + type = C.TYPE_OTHER; + break; + default: + type = -1; + break; + } + } + switch (type) { + case C.TYPE_SS: + return new SsMediaSource.Factory( + new DefaultSsChunkSource.Factory(mediaDataSourceFactory), + new DefaultDataSourceFactory(context, null, mediaDataSourceFactory)) + .createMediaSource(uri); + case C.TYPE_DASH: + return new DashMediaSource.Factory( + new DefaultDashChunkSource.Factory(mediaDataSourceFactory), + new DefaultDataSourceFactory(context, null, mediaDataSourceFactory)) + .createMediaSource(uri); + case C.TYPE_HLS: + return new HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri); + case C.TYPE_OTHER: + return new ExtractorMediaSource.Factory(mediaDataSourceFactory) + .setExtractorsFactory(new DefaultExtractorsFactory()) + .createMediaSource(uri); + default: + { + throw new IllegalStateException("Unsupported type: " + type); + } + } + } + + private void setupVideoPlayer( + EventChannel eventChannel, TextureRegistry.SurfaceTextureEntry textureEntry, Result result) { + + eventChannel.setStreamHandler( + new EventChannel.StreamHandler() { + @Override + public void onListen(Object o, EventChannel.EventSink sink) { + eventSink.setDelegate(sink); + } + + @Override + public void onCancel(Object o) { + eventSink.setDelegate(null); + } + }); + + surface = new Surface(textureEntry.surfaceTexture()); + exoPlayer.setVideoSurface(surface); + setAudioAttributes(exoPlayer); + + exoPlayer.addListener( + new EventListener() { + + @Override + public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) { + if (playbackState == Player.STATE_BUFFERING) { + sendBufferingUpdate(); + } else if (playbackState == Player.STATE_READY) { + if (!isInitialized) { + isInitialized = true; + sendInitialized(); + } + } else if (playbackState == Player.STATE_ENDED) { + Map event = new HashMap<>(); + event.put("event", "completed"); + eventSink.success(event); + } + } + + @Override + public void onPlayerError(final ExoPlaybackException error) { + if (eventSink != null) { + eventSink.error("VideoError", "Video player had error " + error, null); + } + } + }); + + Map reply = new HashMap<>(); + reply.put("textureId", textureEntry.id()); + result.success(reply); + } + + void sendBufferingUpdate() { + Map event = new HashMap<>(); + event.put("event", "bufferingUpdate"); + List range = Arrays.asList(0, exoPlayer.getBufferedPosition()); + // iOS supports a list of buffered ranges, so here is a list with a single range. + event.put("values", Collections.singletonList(range)); + eventSink.success(event); + } + + @SuppressWarnings("deprecation") + private static void setAudioAttributes(SimpleExoPlayer exoPlayer) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + exoPlayer.setAudioAttributes( + new AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MOVIE).build()); + } else { + exoPlayer.setAudioStreamType(C.STREAM_TYPE_MUSIC); + } + } + + void play() { + exoPlayer.setPlayWhenReady(true); + } + + void pause() { + exoPlayer.setPlayWhenReady(false); + } + + void setLooping(boolean value) { + exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF); + } + + void setVolume(double value) { + float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value)); + exoPlayer.setVolume(bracketedValue); + } + + void seekTo(int location) { + exoPlayer.seekTo(location); + } + + long getPosition() { + return exoPlayer.getCurrentPosition(); + } + + @SuppressWarnings("SuspiciousNameCombination") + private void sendInitialized() { + if (isInitialized) { + Map event = new HashMap<>(); + event.put("event", "initialized"); + event.put("duration", exoPlayer.getDuration()); + + if (exoPlayer.getVideoFormat() != null) { + Format videoFormat = exoPlayer.getVideoFormat(); + int width = videoFormat.width; + int height = videoFormat.height; + int rotationDegrees = videoFormat.rotationDegrees; + // Switch the width/height if video was taken in portrait mode + if (rotationDegrees == 90 || rotationDegrees == 270) { + width = exoPlayer.getVideoFormat().height; + height = exoPlayer.getVideoFormat().width; + } + event.put("width", width); + event.put("height", height); + } + eventSink.success(event); + } + } + + void dispose() { + if (isInitialized) { + exoPlayer.stop(); + } + textureEntry.release(); + eventChannel.setStreamHandler(null); + if (surface != null) { + surface.release(); + } + if (exoPlayer != null) { + exoPlayer.release(); + } + } +} diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 5b1f55fe14d6..fdc0511e217b 100644 --- a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -4,322 +4,70 @@ package io.flutter.plugins.videoplayer; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_ALL; -import static com.google.android.exoplayer2.Player.REPEAT_MODE_OFF; - import android.content.Context; -import android.net.Uri; -import android.os.Build; +import android.util.Log; import android.util.LongSparseArray; -import android.view.Surface; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.ExoPlayerFactory; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.Player; -import com.google.android.exoplayer2.Player.EventListener; -import com.google.android.exoplayer2.SimpleExoPlayer; -import com.google.android.exoplayer2.audio.AudioAttributes; -import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; -import com.google.android.exoplayer2.source.ExtractorMediaSource; -import com.google.android.exoplayer2.source.MediaSource; -import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; -import com.google.android.exoplayer2.source.hls.HlsMediaSource; -import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; -import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; -import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; -import com.google.android.exoplayer2.trackselection.TrackSelector; -import com.google.android.exoplayer2.upstream.DataSource; -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; -import com.google.android.exoplayer2.util.Util; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry.Registrar; -import io.flutter.view.FlutterNativeView; +import io.flutter.view.FlutterMain; import io.flutter.view.TextureRegistry; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class VideoPlayerPlugin implements MethodCallHandler { - - private static class VideoPlayer { - private static final String FORMAT_SS = "ss"; - private static final String FORMAT_DASH = "dash"; - private static final String FORMAT_HLS = "hls"; - private static final String FORMAT_OTHER = "other"; - - private SimpleExoPlayer exoPlayer; - - private Surface surface; - - private final TextureRegistry.SurfaceTextureEntry textureEntry; - - private QueuingEventSink eventSink = new QueuingEventSink(); - - private final EventChannel eventChannel; - - private boolean isInitialized = false; - - VideoPlayer( - Context context, - EventChannel eventChannel, - TextureRegistry.SurfaceTextureEntry textureEntry, - String dataSource, - Result result, - String formatHint) { - this.eventChannel = eventChannel; - this.textureEntry = textureEntry; - - TrackSelector trackSelector = new DefaultTrackSelector(); - exoPlayer = ExoPlayerFactory.newSimpleInstance(context, trackSelector); - - Uri uri = Uri.parse(dataSource); - - DataSource.Factory dataSourceFactory; - if (isHTTP(uri)) { - dataSourceFactory = - new DefaultHttpDataSourceFactory( - "ExoPlayer", - null, - DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, - DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, - true); - } else { - dataSourceFactory = new DefaultDataSourceFactory(context, "ExoPlayer"); - } - - MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint, context); - exoPlayer.prepare(mediaSource); - - setupVideoPlayer(eventChannel, textureEntry, result); - } - - private static boolean isHTTP(Uri uri) { - if (uri == null || uri.getScheme() == null) { - return false; - } - String scheme = uri.getScheme(); - return scheme.equals("http") || scheme.equals("https"); - } - - private MediaSource buildMediaSource( - Uri uri, DataSource.Factory mediaDataSourceFactory, String formatHint, Context context) { - int type; - if (formatHint == null) { - type = Util.inferContentType(uri.getLastPathSegment()); - } else { - switch (formatHint) { - case FORMAT_SS: - type = C.TYPE_SS; - break; - case FORMAT_DASH: - type = C.TYPE_DASH; - break; - case FORMAT_HLS: - type = C.TYPE_HLS; - break; - case FORMAT_OTHER: - type = C.TYPE_OTHER; - break; - default: - type = -1; - break; - } - } - switch (type) { - case C.TYPE_SS: - return new SsMediaSource.Factory( - new DefaultSsChunkSource.Factory(mediaDataSourceFactory), - new DefaultDataSourceFactory(context, null, mediaDataSourceFactory)) - .createMediaSource(uri); - case C.TYPE_DASH: - return new DashMediaSource.Factory( - new DefaultDashChunkSource.Factory(mediaDataSourceFactory), - new DefaultDataSourceFactory(context, null, mediaDataSourceFactory)) - .createMediaSource(uri); - case C.TYPE_HLS: - return new HlsMediaSource.Factory(mediaDataSourceFactory).createMediaSource(uri); - case C.TYPE_OTHER: - return new ExtractorMediaSource.Factory(mediaDataSourceFactory) - .setExtractorsFactory(new DefaultExtractorsFactory()) - .createMediaSource(uri); - default: - { - throw new IllegalStateException("Unsupported type: " + type); - } - } - } - - private void setupVideoPlayer( - EventChannel eventChannel, - TextureRegistry.SurfaceTextureEntry textureEntry, - Result result) { - - eventChannel.setStreamHandler( - new EventChannel.StreamHandler() { - @Override - public void onListen(Object o, EventChannel.EventSink sink) { - eventSink.setDelegate(sink); - } - - @Override - public void onCancel(Object o) { - eventSink.setDelegate(null); - } - }); - - surface = new Surface(textureEntry.surfaceTexture()); - exoPlayer.setVideoSurface(surface); - setAudioAttributes(exoPlayer); - - exoPlayer.addListener( - new EventListener() { - - @Override - public void onPlayerStateChanged(final boolean playWhenReady, final int playbackState) { - if (playbackState == Player.STATE_BUFFERING) { - sendBufferingUpdate(); - } else if (playbackState == Player.STATE_READY) { - if (!isInitialized) { - isInitialized = true; - sendInitialized(); - } - } else if (playbackState == Player.STATE_ENDED) { - Map event = new HashMap<>(); - event.put("event", "completed"); - eventSink.success(event); - } - } - - @Override - public void onPlayerError(final ExoPlaybackException error) { - if (eventSink != null) { - eventSink.error("VideoError", "Video player had error " + error, null); - } - } - }); - - Map reply = new HashMap<>(); - reply.put("textureId", textureEntry.id()); - result.success(reply); - } - private void sendBufferingUpdate() { - Map event = new HashMap<>(); - event.put("event", "bufferingUpdate"); - List range = Arrays.asList(0, exoPlayer.getBufferedPosition()); - // iOS supports a list of buffered ranges, so here is a list with a single range. - event.put("values", Collections.singletonList(range)); - eventSink.success(event); - } - - @SuppressWarnings("deprecation") - private static void setAudioAttributes(SimpleExoPlayer exoPlayer) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - exoPlayer.setAudioAttributes( - new AudioAttributes.Builder().setContentType(C.CONTENT_TYPE_MOVIE).build()); - } else { - exoPlayer.setAudioStreamType(C.STREAM_TYPE_MUSIC); - } - } - - void play() { - exoPlayer.setPlayWhenReady(true); - } - - void pause() { - exoPlayer.setPlayWhenReady(false); - } - - void setLooping(boolean value) { - exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF); - } +/** Android platform implementation of the VideoPlayerPlugin. */ +public class VideoPlayerPlugin implements MethodCallHandler, FlutterPlugin { + private static final String TAG = "VideoPlayerPlugin"; + private final LongSparseArray videoPlayers = new LongSparseArray<>(); + private FlutterState flutterState; - void setVolume(double value) { - float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value)); - exoPlayer.setVolume(bracketedValue); - } - - void seekTo(int location) { - exoPlayer.seekTo(location); - } - - long getPosition() { - return exoPlayer.getCurrentPosition(); - } + /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */ + public VideoPlayerPlugin() {} - @SuppressWarnings("SuspiciousNameCombination") - private void sendInitialized() { - if (isInitialized) { - Map event = new HashMap<>(); - event.put("event", "initialized"); - event.put("duration", exoPlayer.getDuration()); - - if (exoPlayer.getVideoFormat() != null) { - Format videoFormat = exoPlayer.getVideoFormat(); - int width = videoFormat.width; - int height = videoFormat.height; - int rotationDegrees = videoFormat.rotationDegrees; - // Switch the width/height if video was taken in portrait mode - if (rotationDegrees == 90 || rotationDegrees == 270) { - width = exoPlayer.getVideoFormat().height; - height = exoPlayer.getVideoFormat().width; - } - event.put("width", width); - event.put("height", height); - } - eventSink.success(event); - } - } - - void dispose() { - if (isInitialized) { - exoPlayer.stop(); - } - textureEntry.release(); - eventChannel.setStreamHandler(null); - if (surface != null) { - surface.release(); - } - if (exoPlayer != null) { - exoPlayer.release(); - } - } + private VideoPlayerPlugin(Registrar registrar) { + this.flutterState = + new FlutterState( + registrar.context(), + registrar.messenger(), + registrar::lookupKeyForAsset, + registrar::lookupKeyForAsset, + registrar.textures()); + flutterState.startListening(this); } + /** Registers this with the stable v1 embedding. Will not respond to lifecycle events. */ public static void registerWith(Registrar registrar) { final VideoPlayerPlugin plugin = new VideoPlayerPlugin(registrar); - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "flutter.io/videoPlayer"); - channel.setMethodCallHandler(plugin); registrar.addViewDestroyListener( - new PluginRegistry.ViewDestroyListener() { - @Override - public boolean onViewDestroy(FlutterNativeView view) { - plugin.onDestroy(); - return false; // We are not interested in assuming ownership of the NativeView. - } + view -> { + plugin.onDestroy(); + return false; // We are not interested in assuming ownership of the NativeView. }); } - private VideoPlayerPlugin(Registrar registrar) { - this.registrar = registrar; - this.videoPlayers = new LongSparseArray<>(); + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + this.flutterState = + new FlutterState( + binding.getApplicationContext(), + binding.getFlutterEngine().getDartExecutor(), + FlutterMain::getLookupKeyForAsset, + FlutterMain::getLookupKeyForAsset, + binding.getFlutterEngine().getRenderer()); + flutterState.startListening(this); } - private final LongSparseArray videoPlayers; - - private final Registrar registrar; + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + if (flutterState == null) { + Log.wtf(TAG, "Detached from the engine before registering to it."); + } + flutterState.stopListening(); + flutterState = null; + } private void disposeAllPlayers() { for (int i = 0; i < videoPlayers.size(); i++) { @@ -339,8 +87,7 @@ private void onDestroy() { @Override public void onMethodCall(MethodCall call, Result result) { - TextureRegistry textures = registrar.textures(); - if (textures == null) { + if (flutterState == null || flutterState.textureRegistry == null) { result.error("no_activity", "video_player plugin requires a foreground activity", null); return; } @@ -350,23 +97,25 @@ public void onMethodCall(MethodCall call, Result result) { break; case "create": { - TextureRegistry.SurfaceTextureEntry handle = textures.createSurfaceTexture(); + TextureRegistry.SurfaceTextureEntry handle = + flutterState.textureRegistry.createSurfaceTexture(); EventChannel eventChannel = new EventChannel( - registrar.messenger(), "flutter.io/videoPlayer/videoEvents" + handle.id()); + flutterState.binaryMessenger, "flutter.io/videoPlayer/videoEvents" + handle.id()); VideoPlayer player; if (call.argument("asset") != null) { String assetLookupKey; if (call.argument("package") != null) { assetLookupKey = - registrar.lookupKeyForAsset(call.argument("asset"), call.argument("package")); + flutterState.keyForAssetAndPackageName.get( + call.argument("asset"), call.argument("package")); } else { - assetLookupKey = registrar.lookupKeyForAsset(call.argument("asset")); + assetLookupKey = flutterState.keyForAsset.get(call.argument("asset")); } player = new VideoPlayer( - registrar.context(), + flutterState.applicationContext, eventChannel, handle, "asset:///" + assetLookupKey, @@ -376,7 +125,7 @@ public void onMethodCall(MethodCall call, Result result) { } else { player = new VideoPlayer( - registrar.context(), + flutterState.applicationContext, eventChannel, handle, call.argument("uri"), @@ -440,4 +189,43 @@ private void onMethodCall(MethodCall call, Result result, long textureId, VideoP break; } } + + private interface KeyForAssetFn { + String get(String asset); + } + + private interface KeyForAssetAndPackageName { + String get(String asset, String packageName); + } + + private static final class FlutterState { + private final Context applicationContext; + private final BinaryMessenger binaryMessenger; + private final KeyForAssetFn keyForAsset; + private final KeyForAssetAndPackageName keyForAssetAndPackageName; + private final TextureRegistry textureRegistry; + private final MethodChannel methodChannel; + + FlutterState( + Context applicationContext, + BinaryMessenger messenger, + KeyForAssetFn keyForAsset, + KeyForAssetAndPackageName keyForAssetAndPackageName, + TextureRegistry textureRegistry) { + this.applicationContext = applicationContext; + this.binaryMessenger = messenger; + this.keyForAsset = keyForAsset; + this.keyForAssetAndPackageName = keyForAssetAndPackageName; + this.textureRegistry = textureRegistry; + methodChannel = new MethodChannel(messenger, "flutter.io/videoPlayer"); + } + + void startListening(VideoPlayerPlugin methodCallHandler) { + methodChannel.setMethodCallHandler(methodCallHandler); + } + + void stopListening() { + methodChannel.setMethodCallHandler(null); + } + } } diff --git a/packages/video_player/example/android/app/src/main/AndroidManifest.xml b/packages/video_player/example/android/app/src/main/AndroidManifest.xml index 914e82b3c894..deec4b6b5b08 100644 --- a/packages/video_player/example/android/app/src/main/AndroidManifest.xml +++ b/packages/video_player/example/android/app/src/main/AndroidManifest.xml @@ -1,27 +1,35 @@ + package="io.flutter.plugins.videoplayerexample"> - + + + + + + + + + + + - - - - - - - - - + diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..f1af8ecd74e7 --- /dev/null +++ b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java @@ -0,0 +1,17 @@ +// 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. + +package io.flutter.plugins.videoplayerexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java index 133c3fa2c898..2a0ae15e5e2f 100644 --- a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java +++ b/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java @@ -4,14 +4,13 @@ package io.flutter.plugins.videoplayerexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.videoplayer.VideoPlayerPlugin; public class MainActivity extends FlutterActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new VideoPlayerPlugin()); } } diff --git a/packages/video_player/example/android/gradle.properties b/packages/video_player/example/android/gradle.properties index 8bd86f680510..a6738207fd15 100644 --- a/packages/video_player/example/android/gradle.properties +++ b/packages/video_player/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart index dea1086593df..196b90846e16 100644 --- a/packages/video_player/example/lib/main.dart +++ b/packages/video_player/example/lib/main.dart @@ -388,11 +388,11 @@ void main() { Container( padding: const EdgeInsets.only(top: 20.0), ), - const Text('With remote m3u8'), + const Text('With remote mp4'), Container( padding: const EdgeInsets.all(20), child: NetworkPlayerLifeCycle( - 'http://184.72.239.149/vod/smil:BigBuckBunny.smil/playlist.m3u8', + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', (BuildContext context, VideoPlayerController controller) => AspectRatioVideo(controller), diff --git a/packages/video_player/example/pubspec.yaml b/packages/video_player/example/pubspec.yaml index da48f06f0796..bcb559b35bb5 100644 --- a/packages/video_player/example/pubspec.yaml +++ b/packages/video_player/example/pubspec.yaml @@ -8,7 +8,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - + flutter_driver: + sdk: flutter + e2e: "^0.2.0" video_player: path: ../ diff --git a/packages/video_player/example/test_driver/video_player_e2e.dart b/packages/video_player/example/test_driver/video_player_e2e.dart new file mode 100644 index 000000000000..e726ab2f410d --- /dev/null +++ b/packages/video_player/example/test_driver/video_player_e2e.dart @@ -0,0 +1,67 @@ +// 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:e2e/e2e.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:video_player/video_player.dart'; + +const Duration _playDuration = Duration(seconds: 1); + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + VideoPlayerController _controller; + + tearDown(() async => _controller.dispose()); + + group('asset videos', () { + setUp(() { + _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); + }); + + testWidgets('can be initialized', (WidgetTester tester) async { + await _controller.initialize(); + + expect(_controller.value.initialized, true); + expect(_controller.value.position, const Duration(seconds: 0)); + expect(_controller.value.isPlaying, false); + expect(_controller.value.duration, + const Duration(seconds: 7, milliseconds: 540)); + }); + + testWidgets('can be played', (WidgetTester tester) async { + await _controller.initialize(); + + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(_controller.value.isPlaying, true); + expect(_controller.value.position, + (Duration position) => position > const Duration(seconds: 0)); + }, skip: Platform.isIOS); + + testWidgets('can seek', (WidgetTester tester) async { + await _controller.initialize(); + + await _controller.seekTo(const Duration(seconds: 3)); + + expect(_controller.value.position, const Duration(seconds: 3)); + }, skip: Platform.isIOS); + + testWidgets('can be paused', (WidgetTester tester) async { + await _controller.initialize(); + + // Play for a second, then pause, and then wait a second. + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + await _controller.pause(); + final Duration pausedPosition = _controller.value.position; + await tester.pumpAndSettle(_playDuration); + + // Verify that we stopped playing after the pause. + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, pausedPosition); + }, skip: Platform.isIOS); + }); +} diff --git a/packages/video_player/example/test_driver/video_player_e2e_test.dart b/packages/video_player/example/test_driver/video_player_e2e_test.dart new file mode 100644 index 000000000000..ccd716607d60 --- /dev/null +++ b/packages/video_player/example/test_driver/video_player_e2e_test.dart @@ -0,0 +1,16 @@ +// 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/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 1b0805be1764..656a81fa8643 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.2+6 +version: 0.10.3 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: @@ -22,4 +22,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.9.1+hotfix.5 <2.0.0" From 56e46aca6b929612333415d781c84d9a9625c7e1 Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Wed, 13 Nov 2019 13:06:08 -0800 Subject: [PATCH 11/47] [google_maps_flutter] Define clang module for iOS, fix analyzer warnings (#2182) --- packages/google_maps_flutter/CHANGELOG.md | 4 ++++ .../example/ios/Runner/AppDelegate.m | 7 ++++--- .../ios/Classes/GoogleMapCircleController.m | 14 +++++++------- .../ios/Classes/GoogleMapController.h | 10 +++++----- .../ios/Classes/GoogleMapController.m | 18 +++++++++--------- .../ios/Classes/GoogleMapMarkerController.m | 14 +++++++------- .../ios/Classes/GoogleMapPolygonController.m | 12 ++++++------ .../ios/Classes/GoogleMapPolylineController.m | 10 +++++----- .../ios/google_maps_flutter.podspec | 4 ++-- packages/google_maps_flutter/pubspec.yaml | 2 +- script/lint_darwin_plugins.sh | 10 +--------- 11 files changed, 51 insertions(+), 54 deletions(-) diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index 2c83c6449da9..6ea04694815a 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.21+11 + +* Define clang module for iOS, fix analyzer warnings. + ## 0.5.21+10 * Cast error.code to unsigned long to avoid using NSInteger as %ld format warnings. diff --git a/packages/google_maps_flutter/example/ios/Runner/AppDelegate.m b/packages/google_maps_flutter/example/ios/Runner/AppDelegate.m index 893deae584b0..6896c5c190b1 100644 --- a/packages/google_maps_flutter/example/ios/Runner/AppDelegate.m +++ b/packages/google_maps_flutter/example/ios/Runner/AppDelegate.m @@ -1,6 +1,7 @@ -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" -#import "GoogleMaps/GoogleMaps.h" +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@import GoogleMaps; @implementation AppDelegate diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapCircleController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapCircleController.m index 92e951200437..6688d4d57695 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapCircleController.m +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapCircleController.m @@ -73,17 +73,17 @@ static CLLocationDistance ToDistance(NSNumber* data) { static void InterpretCircleOptions(NSDictionary* data, id sink, NSObject* registrar) { NSNumber* consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents) { + if (consumeTapEvents != nil) { [sink setConsumeTapEvents:ToBool(consumeTapEvents)]; } NSNumber* visible = data[@"visible"]; - if (visible) { + if (visible != nil) { [sink setVisible:ToBool(visible)]; } NSNumber* zIndex = data[@"zIndex"]; - if (zIndex) { + if (zIndex != nil) { [sink setZIndex:ToInt(zIndex)]; } @@ -93,22 +93,22 @@ static void InterpretCircleOptions(NSDictionary* data, id - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId - arguments:(id _Nullable)args + arguments:(nullable id)args registrar:(NSObject *)registrar; - (void)showAtX:(CGFloat)x Y:(CGFloat)y; - (void)hide; - (void)animateWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate; - (void)moveWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate; -- (GMSCameraPosition *)cameraPosition; +- (nullable GMSCameraPosition *)cameraPosition; @end // Allows the engine to create new Google Map instances. @@ -49,4 +49,4 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithRegistrar:(NSObject *)registrar; @end -NS_ASSUME_NONNULL_END \ No newline at end of file +NS_ASSUME_NONNULL_END diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m index 70a278af45de..ce85887d5d97 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m @@ -65,7 +65,7 @@ - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args registrar:(NSObject*)registrar { - if ([super init]) { + if (self = [super init]) { _viewId = viewId; GMSCameraPosition* camera = ToOptionalCameraPosition(args[@"initialCameraPosition"]); @@ -543,7 +543,7 @@ static void InterpretMapOptions(NSDictionary* data, id [sink setCameraTargetBounds:ToOptionalBounds(cameraTargetBounds)]; } NSNumber* compassEnabled = data[@"compassEnabled"]; - if (compassEnabled) { + if (compassEnabled != nil) { [sink setCompassEnabled:ToBool(compassEnabled)]; } id indoorEnabled = data[@"indoorEnabled"]; @@ -574,31 +574,31 @@ static void InterpretMapOptions(NSDictionary* data, id } NSNumber* rotateGesturesEnabled = data[@"rotateGesturesEnabled"]; - if (rotateGesturesEnabled) { + if (rotateGesturesEnabled != nil) { [sink setRotateGesturesEnabled:ToBool(rotateGesturesEnabled)]; } NSNumber* scrollGesturesEnabled = data[@"scrollGesturesEnabled"]; - if (scrollGesturesEnabled) { + if (scrollGesturesEnabled != nil) { [sink setScrollGesturesEnabled:ToBool(scrollGesturesEnabled)]; } NSNumber* tiltGesturesEnabled = data[@"tiltGesturesEnabled"]; - if (tiltGesturesEnabled) { + if (tiltGesturesEnabled != nil) { [sink setTiltGesturesEnabled:ToBool(tiltGesturesEnabled)]; } NSNumber* trackCameraPosition = data[@"trackCameraPosition"]; - if (trackCameraPosition) { + if (trackCameraPosition != nil) { [sink setTrackCameraPosition:ToBool(trackCameraPosition)]; } NSNumber* zoomGesturesEnabled = data[@"zoomGesturesEnabled"]; - if (zoomGesturesEnabled) { + if (zoomGesturesEnabled != nil) { [sink setZoomGesturesEnabled:ToBool(zoomGesturesEnabled)]; } NSNumber* myLocationEnabled = data[@"myLocationEnabled"]; - if (myLocationEnabled) { + if (myLocationEnabled != nil) { [sink setMyLocationEnabled:ToBool(myLocationEnabled)]; } NSNumber* myLocationButtonEnabled = data[@"myLocationButtonEnabled"]; - if (myLocationButtonEnabled) { + if (myLocationButtonEnabled != nil) { [sink setMyLocationButtonEnabled:ToBool(myLocationButtonEnabled)]; } } diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m index 2f0d4a989b9d..76a420f993db 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m @@ -95,7 +95,7 @@ static CLLocationCoordinate2D ToLocation(NSArray* data) { static void InterpretMarkerOptions(NSDictionary* data, id sink, NSObject* registrar) { NSNumber* alpha = data[@"alpha"]; - if (alpha) { + if (alpha != nil) { [sink setAlpha:ToFloat(alpha)]; } NSArray* anchor = data[@"anchor"]; @@ -103,7 +103,7 @@ static void InterpretMarkerOptions(NSDictionary* data, id sink, NSObject* registrar) { NSNumber* consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents) { + if (consumeTapEvents != nil) { [sink setConsumeTapEvents:ToBool(consumeTapEvents)]; } NSNumber* visible = data[@"visible"]; - if (visible) { + if (visible != nil) { [sink setVisible:ToBool(visible)]; } NSNumber* zIndex = data[@"zIndex"]; - if (zIndex) { + if (zIndex != nil) { [sink setZIndex:ToInt(zIndex)]; } @@ -90,17 +90,17 @@ static void InterpretPolygonOptions(NSDictionary* data, id sink, NSObject* registrar) { NSNumber* consumeTapEvents = data[@"consumeTapEvents"]; - if (consumeTapEvents) { + if (consumeTapEvents != nil) { [sink setConsumeTapEvents:ToBool(consumeTapEvents)]; } NSNumber* visible = data[@"visible"]; - if (visible) { + if (visible != nil) { [sink setVisible:ToBool(visible)]; } NSNumber* zIndex = data[@"zIndex"]; - if (zIndex) { + if (zIndex != nil) { [sink setZIndex:ToInt(zIndex)]; } @@ -87,12 +87,12 @@ static void InterpretPolylineOptions(NSDictionary* data, id 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } end diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index 3fed14e299c0..fc683bb1bba6 100644 --- a/packages/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter -version: 0.5.21+10 +version: 0.5.21+11 dependencies: flutter: diff --git a/script/lint_darwin_plugins.sh b/script/lint_darwin_plugins.sh index 94041c728191..84c5f2a8dcfa 100755 --- a/script/lint_darwin_plugins.sh +++ b/script/lint_darwin_plugins.sh @@ -70,16 +70,8 @@ function lint_packages() { return fi - # TODO: These packages have linter errors. Remove plugins from this list as linter issues are fixed. - local skipped_packages=( - 'google_maps_flutter' - ) - local failure_count=0 - for package_name in "$@"; do - if [[ "${skipped_packages[*]}" =~ "${package_name}" ]]; then - continue - fi + for package_name in "$@"; do lint_package "${package_name}" failure_count+="$?" done From 16e916cd7b431dd74c8ebec7077c99647593ba3f Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Wed, 13 Nov 2019 17:09:07 -0800 Subject: [PATCH 12/47] [url_launcher] DartDoc and test improvements (#2274) - Add a lint to url_launcher and url_launcher_platform_interface to prevent undocumented public members. - Add some unit tests. --- .../url_launcher/url_launcher/CHANGELOG.md | 4 + .../url_launcher/analysis_options.yaml | 11 + .../url_launcher/example/lib/main.dart | 2 + .../url_launcher/url_launcher/pubspec.yaml | 2 +- .../url_launcher/test/url_launcher_test.dart | 328 ++++++++++-------- .../CHANGELOG.md | 4 + .../analysis_options.yaml | 11 + .../lib/url_launcher_platform_interface.dart | 10 +- .../pubspec.yaml | 2 +- 9 files changed, 219 insertions(+), 155 deletions(-) create mode 100644 packages/url_launcher/url_launcher/analysis_options.yaml create mode 100644 packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md index 300d6d9342f5..1a07090992d5 100644 --- a/packages/url_launcher/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/url_launcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 5.2.7 + +* Minor unit test changes and added a lint for public DartDocs. + ## 5.2.6 * Remove AndroidX warnings. diff --git a/packages/url_launcher/url_launcher/analysis_options.yaml b/packages/url_launcher/url_launcher/analysis_options.yaml new file mode 100644 index 000000000000..6c7fd57bb022 --- /dev/null +++ b/packages/url_launcher/url_launcher/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/url_launcher/url_launcher/example/lib/main.dart b/packages/url_launcher/url_launcher/example/lib/main.dart index b4a7e4275bfc..00ab6c5b047d 100644 --- a/packages/url_launcher/url_launcher/example/lib/main.dart +++ b/packages/url_launcher/url_launcher/example/lib/main.dart @@ -2,6 +2,8 @@ // 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'; diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index 2477e118586e..e0c59134f8f2 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL on Android and iOS. Supports web, phone, SMS, and email schemes. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher -version: 5.2.6 +version: 5.2.7 flutter: plugin: diff --git a/packages/url_launcher/url_launcher/test/url_launcher_test.dart b/packages/url_launcher/url_launcher/test/url_launcher_test.dart index e7150bebde05..2481e5a0ee63 100644 --- a/packages/url_launcher/url_launcher/test/url_launcher_test.dart +++ b/packages/url_launcher/url_launcher/test/url_launcher_test.dart @@ -2,8 +2,11 @@ // 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:ui'; +import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; +import 'package:flutter/foundation.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; import 'package:flutter/services.dart' show PlatformException; @@ -11,166 +14,197 @@ import 'package:flutter/services.dart' show PlatformException; void main() { final MockUrlLauncher mock = MockUrlLauncher(); when(mock.isMock).thenReturn(true); - UrlLauncherPlatform.instance = mock; - test('canLaunch', () async { - await canLaunch('http://example.com/'); - expect(verify(mock.canLaunch(captureAny)).captured.single, - 'http://example.com/'); + test('closeWebView default behavior', () async { + await closeWebView(); + verify(mock.closeWebView()); }); - test('launch default behavior', () async { - await launch('http://example.com/'); - expect( - verify(mock.launch( - captureAny, - useSafariVC: captureAnyNamed('useSafariVC'), - useWebView: captureAnyNamed('useWebView'), - enableJavaScript: captureAnyNamed('enableJavaScript'), - enableDomStorage: captureAnyNamed('enableDomStorage'), - universalLinksOnly: captureAnyNamed('universalLinksOnly'), - headers: captureAnyNamed('headers'), - )).captured, - [ - 'http://example.com/', - true, - false, - false, - false, - false, - {}, - ], - ); - }); + group('canLaunch', () { + test('returns true', () async { + when(mock.canLaunch('foo')).thenAnswer((_) => Future.value(true)); - test('launch with headers', () async { - await launch( - 'http://example.com/', - headers: {'key': 'value'}, - ); - expect( - verify(mock.launch( - any, - useSafariVC: anyNamed('useSafariVC'), - useWebView: anyNamed('useWebView'), - enableJavaScript: anyNamed('enableJavaScript'), - enableDomStorage: anyNamed('enableDomStorage'), - universalLinksOnly: anyNamed('universalLinksOnly'), - headers: captureAnyNamed('headers'), - )).captured.single, - {'key': 'value'}, - ); - }); + final bool result = await canLaunch('foo'); - test('launch force SafariVC', () async { - await launch('http://example.com/', forceSafariVC: true); - expect( - verify(mock.launch( - any, - useSafariVC: captureAnyNamed('useSafariVC'), - useWebView: anyNamed('useWebView'), - enableJavaScript: anyNamed('enableJavaScript'), - enableDomStorage: anyNamed('enableDomStorage'), - universalLinksOnly: anyNamed('universalLinksOnly'), - headers: anyNamed('headers'), - )).captured.single, - true, - ); - }); + expect(result, isTrue); + }); - test('launch universal links only', () async { - await launch('http://example.com/', - forceSafariVC: false, universalLinksOnly: true); - expect( - verify(mock.launch( - any, - useSafariVC: captureAnyNamed('useSafariVC'), - useWebView: anyNamed('useWebView'), - enableJavaScript: anyNamed('enableJavaScript'), - enableDomStorage: anyNamed('enableDomStorage'), - universalLinksOnly: captureAnyNamed('universalLinksOnly'), - headers: anyNamed('headers'), - )).captured, - [false, true], - ); - }); + test('returns false', () async { + when(mock.canLaunch('foo')).thenAnswer((_) => Future.value(false)); - test('launch force WebView', () async { - await launch('http://example.com/', forceWebView: true); - expect( - verify(mock.launch( - any, - useSafariVC: anyNamed('useSafariVC'), - useWebView: captureAnyNamed('useWebView'), - enableJavaScript: anyNamed('enableJavaScript'), - enableDomStorage: anyNamed('enableDomStorage'), - universalLinksOnly: anyNamed('universalLinksOnly'), - headers: anyNamed('headers'), - )).captured.single, - true, - ); - }); + final bool result = await canLaunch('foo'); - test('launch force WebView enable javascript', () async { - await launch('http://example.com/', - forceWebView: true, enableJavaScript: true); - expect( - verify(mock.launch( - any, - useSafariVC: anyNamed('useSafariVC'), - useWebView: captureAnyNamed('useWebView'), - enableJavaScript: captureAnyNamed('enableJavaScript'), - enableDomStorage: anyNamed('enableDomStorage'), - universalLinksOnly: anyNamed('universalLinksOnly'), - headers: anyNamed('headers'), - )).captured, - [true, true], - ); + expect(result, isFalse); + }); }); + group('launch', () { + test('requires a non-null urlString', () { + expect(() => launch(null), throwsAssertionError); + }); - test('launch force WebView enable DOM storage', () async { - await launch('http://example.com/', - forceWebView: true, enableDomStorage: true); - expect( - verify(mock.launch( - any, - useSafariVC: anyNamed('useSafariVC'), - useWebView: captureAnyNamed('useWebView'), - enableJavaScript: anyNamed('enableJavaScript'), - enableDomStorage: captureAnyNamed('enableDomStorage'), - universalLinksOnly: anyNamed('universalLinksOnly'), - headers: anyNamed('headers'), - )).captured, - [true, true], - ); - }); + test('default behavior', () async { + await launch('http://flutter.dev/'); + expect( + verify(mock.launch( + captureAny, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: captureAnyNamed('enableJavaScript'), + enableDomStorage: captureAnyNamed('enableDomStorage'), + universalLinksOnly: captureAnyNamed('universalLinksOnly'), + headers: captureAnyNamed('headers'), + )).captured, + [ + 'http://flutter.dev/', + true, + false, + false, + false, + false, + {}, + ], + ); + }); - test('launch force SafariVC to false', () async { - await launch('http://example.com/', forceSafariVC: false); - expect( - // ignore: missing_required_param - verify(mock.launch( - any, - useSafariVC: captureAnyNamed('useSafariVC'), - useWebView: anyNamed('useWebView'), - enableJavaScript: anyNamed('enableJavaScript'), - enableDomStorage: anyNamed('enableDomStorage'), - universalLinksOnly: anyNamed('universalLinksOnly'), - headers: anyNamed('headers'), - )).captured.single, - false, - ); - }); + test('with headers', () async { + await launch( + 'http://flutter.dev/', + headers: {'key': 'value'}, + ); + expect( + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: captureAnyNamed('headers'), + )).captured.single, + {'key': 'value'}, + ); + }); - test('cannot launch a non-web in webview', () async { - expect(() async => await launch('tel:555-555-5555', forceWebView: true), - throwsA(isA())); - }); + test('force SafariVC', () async { + await launch('http://flutter.dev/', forceSafariVC: true); + expect( + verify(mock.launch( + any, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured.single, + true, + ); + }); - test('closeWebView default behavior', () async { - await closeWebView(); - verify(mock.closeWebView()); + test('universal links only', () async { + await launch('http://flutter.dev/', + forceSafariVC: false, universalLinksOnly: true); + expect( + verify(mock.launch( + any, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: captureAnyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured, + [false, true], + ); + }); + + test('force WebView', () async { + await launch('http://flutter.dev/', forceWebView: true); + expect( + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured.single, + true, + ); + }); + + test('force WebView enable javascript', () async { + await launch('http://flutter.dev/', + forceWebView: true, enableJavaScript: true); + expect( + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: captureAnyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured, + [true, true], + ); + }); + + test('force WebView enable DOM storage', () async { + await launch('http://flutter.dev/', + forceWebView: true, enableDomStorage: true); + expect( + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: captureAnyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured, + [true, true], + ); + }); + + test('force SafariVC to false', () async { + await launch('http://flutter.dev/', forceSafariVC: false); + expect( + // ignore: missing_required_param + verify(mock.launch( + any, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured.single, + false, + ); + }); + + test('cannot launch a non-web in webview', () async { + expect(() async => await launch('tel:555-555-5555', forceWebView: true), + throwsA(isA())); + }); + + test('controls system UI when changing statusBarBrightness', () async { + final TestWidgetsFlutterBinding binding = + TestWidgetsFlutterBinding.ensureInitialized(); + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + binding.renderView.automaticSystemUiAdjustment = true; + final Future launchResult = + launch('http://flutter.dev/', statusBarBrightness: Brightness.dark); + + // Should take over control of the automaticSystemUiAdjustment while it's + // pending, then restore it back to normal after the launch finishes. + expect(binding.renderView.automaticSystemUiAdjustment, isFalse); + await launchResult; + expect(binding.renderView.automaticSystemUiAdjustment, isTrue); + }); }); } diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md index 7aa8ee011524..18d373d183f2 100644 --- a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.3 + +* Minor DartDoc changes and add a lint for missing DartDocs. + ## 1.0.2 * Use package URI in test directory to import code from lib. diff --git a/packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml b/packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml new file mode 100644 index 000000000000..6c7fd57bb022 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart index a17aa0626126..64eeec7cae2d 100644 --- a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart +++ b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart @@ -24,17 +24,15 @@ abstract class UrlLauncherPlatform { @visibleForTesting bool get isMock => false; + static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); + /// The default instance of [UrlLauncherPlatform] to use. /// - /// Platform-specific plugins should override this with their own - /// platform-specific class that extends [UrlLauncherPlatform] when they - /// register themselves. - /// /// Defaults to [MethodChannelUrlLauncher]. - static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); - static UrlLauncherPlatform get instance => _instance; + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [UrlLauncherPlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 static set instance(UrlLauncherPlatform instance) { diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml index 192fbe7e8066..3f7aa4832c41 100644 --- a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.2 +version: 1.0.3 dependencies: flutter: From 2c92b3fa7836ab013f8dac567a516cae3ad8857c Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Wed, 13 Nov 2019 17:59:31 -0800 Subject: [PATCH 13/47] [battery] Update and migrate iOS example project (#2089) --- packages/battery/CHANGELOG.md | 4 ++ .../ios/Runner.xcodeproj/project.pbxproj | 70 +++++++++---------- .../xcshareddata/xcschemes/Runner.xcscheme | 10 +-- packages/battery/pubspec.yaml | 2 +- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/packages/battery/CHANGELOG.md b/packages/battery/CHANGELOG.md index c6381ec0282e..12a830069fcb 100644 --- a/packages/battery/CHANGELOG.md +++ b/packages/battery/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.1+4 + +* Update and migrate iOS example project. + ## 0.3.1+3 * Remove AndroidX warning. diff --git a/packages/battery/example/ios/Runner.xcodeproj/project.pbxproj b/packages/battery/example/ios/Runner.xcodeproj/project.pbxproj index 235e8749805d..aa42a8509346 100644 --- a/packages/battery/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/battery/example/ios/Runner.xcodeproj/project.pbxproj @@ -9,19 +9,16 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 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 */; }; - EA987CF1DD05781B010B5D39 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B1E864018968B8D5FA44E86 /* Pods_Runner.framework */; }; + CC33A11108F15DB5F0C6C7AD /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E2CD29898079A0E658445A5 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -42,10 +39,10 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1E2CD29898079A0E658445A5 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; - 4B1E864018968B8D5FA44E86 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5F92487ECF695372E82D90C5 /* 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 = ""; }; 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 = ""; }; @@ -58,6 +55,7 @@ 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 = ""; }; + BF850F5DC44F7AE2B245B994 /* 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 */ @@ -67,7 +65,7 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - EA987CF1DD05781B010B5D39 /* Pods_Runner.framework in Frameworks */, + CC33A11108F15DB5F0C6C7AD /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -77,7 +75,7 @@ 1C99224A167BC35DA0CD0913 /* Frameworks */ = { isa = PBXGroup; children = ( - 4B1E864018968B8D5FA44E86 /* Pods_Runner.framework */, + 1E2CD29898079A0E658445A5 /* libPods-Runner.a */, ); name = Frameworks; sourceTree = ""; @@ -85,6 +83,8 @@ 571753FC2D526E56A295E627 /* Pods */ = { isa = PBXGroup; children = ( + 5F92487ECF695372E82D90C5 /* Pods-Runner.debug.xcconfig */, + BF850F5DC44F7AE2B245B994 /* Pods-Runner.release.xcconfig */, ); name = Pods; sourceTree = ""; @@ -94,7 +94,6 @@ children = ( 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -161,7 +160,6 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 4096151B6BA12D6D4D7DD96A /* [CP] Embed Pods Frameworks */, - 7C9CC6394B25E69B476E302B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -178,18 +176,17 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 1100; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - DevelopmentTeam = 3GRKCVVJ22; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -211,10 +208,7 @@ buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -249,22 +243,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 7C9CC6394B25E69B476E302B /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -287,13 +266,16 @@ 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"; + 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 */ @@ -336,19 +318,28 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 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; @@ -384,19 +375,28 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 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; @@ -425,9 +425,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 3GRKCVVJ22; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -448,9 +446,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = 3GRKCVVJ22; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/battery/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/battery/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c9580788197..3bb3697ef41c 100644 --- a/packages/battery/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/battery/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - homepage: https://github.com/flutter/plugins/tree/master/packages/battery -version: 0.3.1+3 +version: 0.3.1+4 flutter: plugin: From 4a9450325055455875fd657d07f2c5c06f8c5689 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 13 Nov 2019 19:23:08 -0800 Subject: [PATCH 14/47] Update cirrus to create IOS simulator on 13.2 an xCode 11 (#2275) --- .cirrus.yml | 4 ++-- packages/e2e/example/pubspec.yaml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index a3fc1382cc04..af37fc630c2f 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -82,7 +82,7 @@ task: task: use_compute_credits: $CIRRUS_USER_COLLABORATOR == 'true' osx_instance: - image: mojave-xcode-10.2-flutter + image: mojave-xcode-11.2.1-flutter setup_script: - pod repo update upgrade_script: @@ -94,7 +94,7 @@ task: activate_script: pub global activate flutter_plugin_tools create_simulator_script: - xcrun simctl list - - xcrun simctl create Flutter-iPhone com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-12-2 | xargs xcrun simctl boot + - xcrun simctl create Flutter-iPhone com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-13-2 | xargs xcrun simctl boot matrix: - name: build_all_plugins_ipa script: diff --git a/packages/e2e/example/pubspec.yaml b/packages/e2e/example/pubspec.yaml index 456a62f2b36a..433fe782ad5e 100644 --- a/packages/e2e/example/pubspec.yaml +++ b/packages/e2e/example/pubspec.yaml @@ -1,5 +1,6 @@ name: e2e_example description: Demonstrates how to use the e2e plugin. +version: 0.0.1 publish_to: 'none' environment: From d22502af8f0f9fde5f8cefee95cea5a9bc9bd094 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Thu, 14 Nov 2019 09:34:29 -0800 Subject: [PATCH 15/47] [webview_flutter] Add async NavigationDelegates (#2257) Previously all navigation decisions needed to be made synchronously in Dart. The platform layers are already waiting for the MethodChannel callback, so just changed the return type to be `FutureOr'. This is a change to the method signature of `WebViewPlatformCallbacksHandler#onNavigationRequest`, but that interface appears to only be meant to be implemented internally to the plugin. --- packages/webview_flutter/CHANGELOG.md | 5 + .../test_driver/webview_flutter_e2e.dart | 117 ++++++++++++++++++ .../lib/platform_interface.dart | 2 +- .../lib/src/webview_method_channel.dart | 2 +- .../webview_flutter/lib/webview_flutter.dart | 8 +- packages/webview_flutter/pubspec.yaml | 2 +- 6 files changed, 130 insertions(+), 6 deletions(-) diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 17a4eb0b0916..e577b30f53af 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.16 + +* Add support for async NavigationDelegates. Synchronous NavigationDelegates + should still continue to function without any change in behavior. + ## 0.3.15+3 * Re-land support for the v2 Android embedding. This correctly sets the minimum diff --git a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart index a5d4d7de66fe..324ab6140de5 100644 --- a/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart +++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart @@ -494,6 +494,123 @@ void main() { final String title = await controller.getTitle(); expect(title, 'Some title'); }); + + group('NavigationDelegate', () { + final String blankPage = ""; + final String blankPageEncoded = 'data:text/html;charset=utf-8;base64,' + + base64Encode(const Utf8Encoder().convert(blankPage)); + + testWidgets('can allow requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return (request.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('location.href = "https://www.google.com/"'); + + await pageLoads.stream.first; // Wait for the next page load. + final String currentUrl = await controller.currentUrl(); + expect(currentUrl, 'https://www.google.com/'); + }); + + testWidgets('can block requests', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) { + return (request.url.contains('youtube.com')) + ? NavigationDecision.prevent + : NavigationDecision.navigate; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('location.href = "https://www.youtube.com/"'); + + // There should never be any second page load, since our new URL is + // blocked. Still wait for a potential page change for some time in order + // to give the test a chance to fail. + await pageLoads.stream.first + .timeout(const Duration(milliseconds: 500), onTimeout: () => null); + final String currentUrl = await controller.currentUrl(); + expect(currentUrl, isNot(contains('youtube.com'))); + }); + + testWidgets('supports asynchronous decisions', (WidgetTester tester) async { + final Completer controllerCompleter = + Completer(); + final StreamController pageLoads = + StreamController.broadcast(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WebView( + key: GlobalKey(), + initialUrl: blankPageEncoded, + onWebViewCreated: (WebViewController controller) { + controllerCompleter.complete(controller); + }, + javascriptMode: JavascriptMode.unrestricted, + navigationDelegate: (NavigationRequest request) async { + NavigationDecision decision = NavigationDecision.prevent; + decision = await Future.delayed( + const Duration(milliseconds: 10), + () => NavigationDecision.navigate); + return decision; + }, + onPageFinished: (String url) => pageLoads.add(url), + ), + ), + ); + + await pageLoads.stream.first; // Wait for initial page load. + final WebViewController controller = await controllerCompleter.future; + await controller + .evaluateJavascript('location.href = "https://www.google.com"'); + + await pageLoads.stream.first; // Wait for second page to load. + final String currentUrl = await controller.currentUrl(); + expect(currentUrl, 'https://www.google.com/'); + }); + }); } // JavaScript booleans evaluate to different string values on Android and iOS. diff --git a/packages/webview_flutter/lib/platform_interface.dart b/packages/webview_flutter/lib/platform_interface.dart index 7e82bae91138..cf6a2fd933f7 100644 --- a/packages/webview_flutter/lib/platform_interface.dart +++ b/packages/webview_flutter/lib/platform_interface.dart @@ -21,7 +21,7 @@ abstract class WebViewPlatformCallbacksHandler { /// Invoked by [WebViewPlatformController] when a navigation request is pending. /// /// If true is returned the navigation is allowed, otherwise it is blocked. - bool onNavigationRequest({String url, bool isForMainFrame}); + FutureOr onNavigationRequest({String url, bool isForMainFrame}); /// Invoked by [WebViewPlatformController] when a page has finished loading. void onPageFinished(String url); diff --git a/packages/webview_flutter/lib/src/webview_method_channel.dart b/packages/webview_flutter/lib/src/webview_method_channel.dart index c2949cc77a2a..2bb7470a1145 100644 --- a/packages/webview_flutter/lib/src/webview_method_channel.dart +++ b/packages/webview_flutter/lib/src/webview_method_channel.dart @@ -31,7 +31,7 @@ class MethodChannelWebViewPlatform implements WebViewPlatformController { _platformCallbacksHandler.onJavaScriptChannelMessage(channel, message); return true; case 'navigationRequest': - return _platformCallbacksHandler.onNavigationRequest( + return await _platformCallbacksHandler.onNavigationRequest( url: call.arguments['url'], isForMainFrame: call.arguments['isForMainFrame'], ); diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index cd5ca46701d7..11541d322dfb 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -67,7 +67,8 @@ enum NavigationDecision { /// `navigation` should be handled. /// /// See also: [WebView.navigationDelegate]. -typedef NavigationDecision NavigationDelegate(NavigationRequest navigation); +typedef FutureOr NavigationDelegate( + NavigationRequest navigation); /// Signature for when a [WebView] has finished loading a page. typedef void PageFinishedCallback(String url); @@ -439,11 +440,12 @@ class _PlatformCallbacksHandler implements WebViewPlatformCallbacksHandler { } @override - bool onNavigationRequest({String url, bool isForMainFrame}) { + FutureOr onNavigationRequest({String url, bool isForMainFrame}) async { final NavigationRequest request = NavigationRequest._(url: url, isForMainFrame: isForMainFrame); final bool allowNavigation = _widget.navigationDelegate == null || - _widget.navigationDelegate(request) == NavigationDecision.navigate; + await _widget.navigationDelegate(request) == + NavigationDecision.navigate; return allowNavigation; } diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index 4d4df2676f03..4883f049f759 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 0.3.15+3 +version: 0.3.16 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter From 1b5eae5135cae27e103095249b64dad8212f74eb Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Thu, 14 Nov 2019 17:30:15 -0800 Subject: [PATCH 16/47] [google_sign_in] Port plugin to use the federated Platform Interface (#2266) * Port google_sign_in.dart to use the federated Platform Interface, instead of MethodChannels. * Ignore google_sign_in_platform_interface on all-plugins-app call. * Refactor endlessly growing --exclude param value into a string that gets generated for a list of plugin names. --- .../google_sign_in/CHANGELOG.md | 4 + .../google_sign_in/lib/google_sign_in.dart | 138 +++++++++--------- .../google_sign_in/pubspec.yaml | 3 +- .../test/google_sign_in_test.dart | 5 +- script/build_all_plugins_app.sh | 10 +- 5 files changed, 89 insertions(+), 71 deletions(-) diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md index 1f7f5b605bb1..3ea1a59c38d3 100644 --- a/packages/google_sign_in/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.14 + +* Port plugin code to use the federated Platform Interface, instead of a MethodChannel directly. + ## 4.0.13 * Fix `GoogleUserCircleAvatar` to handle new style profile image URLs. diff --git a/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart b/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart index f1e1db21801e..7556c1006c2f 100644 --- a/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart +++ b/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart @@ -5,38 +5,36 @@ import 'dart:async'; import 'dart:ui' show hashValues; -import 'package:flutter/services.dart' show MethodChannel, PlatformException; -import 'package:meta/meta.dart' show visibleForTesting; +import 'package:flutter/services.dart' show PlatformException; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'src/common.dart'; export 'src/common.dart'; export 'widgets.dart'; -enum SignInOption { standard, games } - class GoogleSignInAuthentication { GoogleSignInAuthentication._(this._data); - final Map _data; + final GoogleSignInTokenData _data; /// An OpenID Connect ID token that identifies the user. - String get idToken => _data['idToken']; + String get idToken => _data.idToken; /// The OAuth2 access token to access Google services. - String get accessToken => _data['accessToken']; + String get accessToken => _data.accessToken; @override String toString() => 'GoogleSignInAuthentication:$_data'; } class GoogleSignInAccount implements GoogleIdentity { - GoogleSignInAccount._(this._googleSignIn, Map data) - : displayName = data['displayName'], - email = data['email'], - id = data['id'], - photoUrl = data['photoUrl'], - _idToken = data['idToken'] { + GoogleSignInAccount._(this._googleSignIn, GoogleSignInUserData data) + : displayName = data.displayName, + email = data.email, + id = data.id, + photoUrl = data.photoUrl, + _idToken = data.idToken { assert(id != null); } @@ -78,18 +76,16 @@ class GoogleSignInAccount implements GoogleIdentity { throw StateError('User is no longer signed in.'); } - final Map response = - await GoogleSignIn.channel.invokeMapMethod( - 'getTokens', - { - 'email': email, - 'shouldRecoverAuth': true, - }, + final GoogleSignInTokenData response = + await GoogleSignInPlatform.instance.getTokens( + email: email, + shouldRecoverAuth: true, ); + // On Android, there isn't an API for refreshing the idToken, so re-use // the one we obtained on login. - if (response['idToken'] == null) { - response['idToken'] = _idToken; + if (response.idToken == null) { + response.idToken = _idToken; } return GoogleSignInAuthentication._(response); } @@ -108,10 +104,7 @@ class GoogleSignInAccount implements GoogleIdentity { /// this method and grab `authHeaders` once again. Future clearAuthCache() async { final String token = (await authentication).accessToken; - await GoogleSignIn.channel.invokeMethod( - 'clearAuthCache', - {'token': token}, - ); + await GoogleSignInPlatform.instance.clearAuthCache(token: token); } @override @@ -146,7 +139,7 @@ class GoogleSignIn { /// Initializes global sign-in configuration settings. /// /// The [signInOption] determines the user experience. [SigninOption.games] - /// must not be used on iOS. + /// is only supported on Android. /// /// The list of [scopes] are OAuth scope codes to request when signing in. /// These scope codes will determine the level of data access that is granted @@ -157,18 +150,25 @@ class GoogleSignIn { /// The [hostedDomain] argument specifies a hosted domain restriction. By /// setting this, sign in will be restricted to accounts of the user in the /// specified domain. By default, the list of accounts will not be restricted. - GoogleSignIn({this.signInOption, this.scopes, this.hostedDomain}); + GoogleSignIn({ + this.signInOption = SignInOption.standard, + this.scopes = const [], + this.hostedDomain, + }); /// Factory for creating default sign in user experience. - factory GoogleSignIn.standard({List scopes, String hostedDomain}) { + factory GoogleSignIn.standard({ + List scopes = const [], + String hostedDomain, + }) { return GoogleSignIn( signInOption: SignInOption.standard, scopes: scopes, hostedDomain: hostedDomain); } - /// Factory for creating sign in suitable for games. This option must not be - /// used on iOS because the games API is not supported. + /// Factory for creating sign in suitable for games. This option is only + /// supported on Android. factory GoogleSignIn.games() { return GoogleSignIn(signInOption: SignInOption.games); } @@ -186,13 +186,8 @@ class GoogleSignIn { /// Error code indicating that attempt to sign in failed. static const String kSignInFailedError = 'sign_in_failed'; - /// The [MethodChannel] over which this class communicates. - @visibleForTesting - static const MethodChannel channel = - MethodChannel('plugins.flutter.io/google_sign_in'); - - /// Option to determine the sign in user experience. [SignInOption.games] must - /// not be used on iOS. + /// Option to determine the sign in user experience. [SignInOption.games] is + /// only supported on Android. final SignInOption signInOption; /// The list of [scopes] are OAuth scope codes requested when signing in. @@ -211,12 +206,12 @@ class GoogleSignIn { // Future that completes when we've finished calling `init` on the native side Future _initialization; - Future _callMethod(String method) async { + Future _callMethod(Function method) async { await _ensureInitialized(); - final Map response = - await channel.invokeMapMethod(method); - return _setCurrentUser(response != null && response.isNotEmpty + final dynamic response = await method(); + + return _setCurrentUser(response != null && response is GoogleSignInUserData ? GoogleSignInAccount._(this, response) : null); } @@ -230,16 +225,14 @@ class GoogleSignIn { } Future _ensureInitialized() { - return _initialization ??= - channel.invokeMethod('init', { - 'signInOption': (signInOption ?? SignInOption.standard).toString(), - 'scopes': scopes ?? [], - 'hostedDomain': hostedDomain, - }) - ..catchError((dynamic _) { - // Invalidate initialization if it errored out. - _initialization = null; - }); + return _initialization ??= GoogleSignInPlatform.instance.init( + signInOption: signInOption, + scopes: scopes, + hostedDomain: hostedDomain, + )..catchError((dynamic _) { + // Invalidate initialization if it errors out. + _initialization = null; + }); } /// The most recently scheduled method call. @@ -251,6 +244,7 @@ class GoogleSignIn { final Completer completer = Completer(); future.whenComplete(completer.complete).catchError((dynamic _) { // Ignore if previous call completed with an error. + // TODO: Should we log errors here, if debug or similar? }); return completer.future; } @@ -259,26 +253,29 @@ class GoogleSignIn { /// /// At most one in flight call is allowed to prevent concurrent (out of order) /// updates to [currentUser] and [onCurrentUserChanged]. - Future _addMethodCall(String method) async { + /// + /// The optional, named parameter [canSkipCall] lets the plugin know that the + /// method call may be skipped, if there's already [_currentUser] information. + /// This is used from the [signIn] and [signInSilently] methods. + Future _addMethodCall( + Function method, { + bool canSkipCall = false, + }) async { Future response; if (_lastMethodCall == null) { response = _callMethod(method); } else { response = _lastMethodCall.then((_) { // If after the last completed call `currentUser` is not `null` and requested - // method is a sign in method, re-use the same authenticated user + // method can be skipped (`canSkipCall`), re-use the same authenticated user // instead of making extra call to the native side. - const List kSignInMethods = [ - 'signIn', - 'signInSilently' - ]; - if (kSignInMethods.contains(method) && _currentUser != null) { + if (canSkipCall && _currentUser != null) { return _currentUser; - } else { - return _callMethod(method); } + return _callMethod(method); }); } + // Add the current response to the currently running Promise of all pending responses _lastMethodCall = _waitFor(response); return response; } @@ -303,10 +300,12 @@ class GoogleSignIn { /// returned Future completes with [PlatformException] whose `code` can be /// either [kSignInRequiredError] (when there is no authenticated user) or /// [kSignInFailedError] (when an unknown error occurred). - Future signInSilently( - {bool suppressErrors = true}) async { + Future signInSilently({ + bool suppressErrors = true, + }) async { try { - return await _addMethodCall('signInSilently'); + return await _addMethodCall(GoogleSignInPlatform.instance.signInSilently, + canSkipCall: true); } catch (_) { if (suppressErrors) { return null; @@ -319,7 +318,7 @@ class GoogleSignIn { /// Returns a future that resolves to whether a user is currently signed in. Future isSignedIn() async { await _ensureInitialized(); - return await channel.invokeMethod('isSignedIn'); + return GoogleSignInPlatform.instance.isSignedIn(); } /// Starts the interactive sign-in process. @@ -333,16 +332,19 @@ class GoogleSignIn { /// /// Re-authentication can be triggered only after [signOut] or [disconnect]. Future signIn() { - final Future result = _addMethodCall('signIn'); + final Future result = + _addMethodCall(GoogleSignInPlatform.instance.signIn, canSkipCall: true); bool isCanceled(dynamic error) => error is PlatformException && error.code == kSignInCanceledError; return result.catchError((dynamic _) => null, test: isCanceled); } /// Marks current user as being in the signed out state. - Future signOut() => _addMethodCall('signOut'); + Future signOut() => + _addMethodCall(GoogleSignInPlatform.instance.signOut); /// Disconnects the current user from the app and revokes previous /// authentication. - Future disconnect() => _addMethodCall('disconnect'); + Future disconnect() => + _addMethodCall(GoogleSignInPlatform.instance.disconnect); } diff --git a/packages/google_sign_in/google_sign_in/pubspec.yaml b/packages/google_sign_in/google_sign_in/pubspec.yaml index 833ef6733d2d..30e2b88016bf 100644 --- a/packages/google_sign_in/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in -version: 4.0.13 +version: 4.0.14 flutter: plugin: @@ -12,6 +12,7 @@ flutter: pluginClass: GoogleSignInPlugin dependencies: + google_sign_in_platform_interface: ^1.0.0 flutter: sdk: flutter meta: ^1.0.4 diff --git a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart index 108edf9c892b..a85fb0f27e42 100755 --- a/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart +++ b/packages/google_sign_in/google_sign_in/test/google_sign_in_test.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; import 'package:google_sign_in/google_sign_in.dart'; import 'package:google_sign_in/testing.dart'; @@ -391,7 +392,9 @@ void main() { GoogleSignIn googleSignIn; setUp(() { - GoogleSignIn.channel.setMockMethodCallHandler( + final MethodChannelGoogleSignIn platformInstance = + GoogleSignInPlatform.instance; + platformInstance.channel.setMockMethodCallHandler( (FakeSignInBackend()..user = kUserData).handleMethodCall); googleSignIn = GoogleSignIn(); }); diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index 5ba4b6e5ce02..cdcb02a02140 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -10,7 +10,15 @@ readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" source "$SCRIPT_DIR/common.sh" check_changed_packages > /dev/null -(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude instrumentation_adapter,url_launcher_platform_interface) +readonly EXCLUDED_PLUGINS_LIST=( + "instrumentation_adapter" + "url_launcher_platform_interface" + "google_sign_in_platform_interface" +) +# Comma-separated string of the list above +readonly EXCLUDED=$(IFS=, ; echo "${EXCLUDED_PLUGINS_LIST[*]}") + +(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude $EXCLUDED) function error() { echo "$@" 1>&2 From 8380988745e4bfe6c8d547272b4fe1c3193514be Mon Sep 17 00:00:00 2001 From: Mehmet Fidanboylu Date: Fri, 15 Nov 2019 11:46:10 -0800 Subject: [PATCH 17/47] [connectivity] Fix reachability stream for iOS (#2281) --- packages/connectivity/CHANGELOG.md | 4 ++++ packages/connectivity/ios/Classes/ConnectivityPlugin.m | 9 +++++++-- packages/connectivity/pubspec.yaml | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index 17e49e25cbd8..0e1ea814907a 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.5+6 + +* Fix singleton Reachability problem on iOS. + ## 0.4.5+5 * Add an analyzer check for the public documentation. diff --git a/packages/connectivity/ios/Classes/ConnectivityPlugin.m b/packages/connectivity/ios/Classes/ConnectivityPlugin.m index c69871175b01..100e70b44ec6 100644 --- a/packages/connectivity/ios/Classes/ConnectivityPlugin.m +++ b/packages/connectivity/ios/Classes/ConnectivityPlugin.m @@ -22,6 +22,7 @@ @interface FLTConnectivityPlugin () *)registrar { @@ -174,12 +175,16 @@ - (FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink selector:@selector(onReachabilityDidChange:) name:kReachabilityChangedNotification object:nil]; - [[Reachability reachabilityForInternetConnection] startNotifier]; + _reachabilityForInternetConnection = [Reachability reachabilityForInternetConnection]; + [_reachabilityForInternetConnection startNotifier]; return nil; } - (FlutterError*)onCancelWithArguments:(id)arguments { - [[Reachability reachabilityForInternetConnection] stopNotifier]; + if (_reachabilityForInternetConnection) { + [_reachabilityForInternetConnection stopNotifier]; + _reachabilityForInternetConnection = nil; + } [[NSNotificationCenter defaultCenter] removeObserver:self]; _eventSink = nil; return nil; diff --git a/packages/connectivity/pubspec.yaml b/packages/connectivity/pubspec.yaml index c68109d835da..8dce3c8867b6 100644 --- a/packages/connectivity/pubspec.yaml +++ b/packages/connectivity/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity -version: 0.4.5+5 +version: 0.4.5+6 flutter: plugin: From 953e50d1b3baec2878e8867d42cd0f43ac5e851d Mon Sep 17 00:00:00 2001 From: Miguel Ruivo Date: Tue, 19 Nov 2019 18:50:23 +0000 Subject: [PATCH 18/47] [image_picker] Fix a crash when picking video on iOS 13 and above. (#2083) --- packages/image_picker/CHANGELOG.md | 4 ++++ .../ios/Classes/ImagePickerPlugin.m | 22 +++++++++++++++++++ packages/image_picker/pubspec.yaml | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index d8a170e47499..11c48e8724bc 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.2 + +* iOS: Fixes an issue where picking conent from Gallery would result in a crash on iOS 13. + ## 0.6.1+11 * Stability and Maintainability: update documentations, add unit tests. diff --git a/packages/image_picker/ios/Classes/ImagePickerPlugin.m b/packages/image_picker/ios/Classes/ImagePickerPlugin.m index a23e13918148..5ac31a695a58 100644 --- a/packages/image_picker/ios/Classes/ImagePickerPlugin.m +++ b/packages/image_picker/ios/Classes/ImagePickerPlugin.m @@ -241,8 +241,30 @@ - (void)imagePickerController:(UIImagePickerController *)picker return; } if (videoURL != nil) { + if (@available(iOS 13.0, *)) { + NSString *fileName = [videoURL lastPathComponent]; + NSURL *destination = + [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]]; + + if ([[NSFileManager defaultManager] isReadableFileAtPath:[videoURL path]]) { + NSError *error; + if (![[videoURL path] isEqualToString:[destination path]]) { + [[NSFileManager defaultManager] copyItemAtURL:videoURL toURL:destination error:&error]; + + if (error) { + self.result([FlutterError errorWithCode:@"flutter_image_picker_copy_video_error" + message:@"Could not cache the video file." + details:nil]); + self.result = nil; + return; + } + } + videoURL = destination; + } + } self.result(videoURL.path); self.result = nil; + } else { UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage]; if (image == nil) { diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml index 998d921005a1..bca5a9df2059 100755 --- a/packages/image_picker/pubspec.yaml +++ b/packages/image_picker/pubspec.yaml @@ -5,7 +5,7 @@ authors: - Flutter Team - Rhodes Davis Jr. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.6.1+11 +version: 0.6.2 flutter: plugin: From dd9eb8bfe5ae6c2861a013038f969f2d2f69d5b7 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 19 Nov 2019 11:39:28 -0800 Subject: [PATCH 19/47] Add google_sign_in_web (0.8.0) plugin. (#2280) * Look at the README.md of this package for usage instructions. Please file issues for platform: web so we see them! --- .../google_sign_in_web/CHANGELOG.md | 3 + .../google_sign_in/google_sign_in_web/LICENSE | 26 + .../google_sign_in_web/README.md | 80 +++ .../google_sign_in_web/analysis_options.yaml | 5 + .../lib/google_sign_in_web.dart | 151 ++++++ .../lib/src/generated/gapi.dart | 459 +++++++++++++++++ .../lib/src/generated/gapiauth2.dart | 464 ++++++++++++++++++ .../google_sign_in_web/lib/src/load_gapi.dart | 55 +++ .../google_sign_in_web/lib/src/utils.dart | 42 ++ .../google_sign_in_web/pubspec.yaml | 31 ++ .../google_sign_in_web/test/auth2_test.dart | 77 +++ .../test/gapi_load_test.dart | 36 ++ .../test/gapi_mocks/gapi_mocks.dart | 14 + .../test/gapi_mocks/src/auth2_init.dart | 42 ++ .../test/gapi_mocks/src/gapi.dart | 12 + .../test/gapi_mocks/src/gapi_load.dart | 8 + .../test/gapi_mocks/src/google_user.dart | 25 + .../test/gapi_mocks/src/test_iife.dart | 15 + .../google_sign_in_web/test/utils.dart | 10 + 19 files changed, 1555 insertions(+) create mode 100644 packages/google_sign_in/google_sign_in_web/CHANGELOG.md create mode 100644 packages/google_sign_in/google_sign_in_web/LICENSE create mode 100644 packages/google_sign_in/google_sign_in_web/README.md create mode 100644 packages/google_sign_in/google_sign_in_web/analysis_options.yaml create mode 100644 packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart create mode 100644 packages/google_sign_in/google_sign_in_web/lib/src/utils.dart create mode 100644 packages/google_sign_in/google_sign_in_web/pubspec.yaml create mode 100644 packages/google_sign_in/google_sign_in_web/test/auth2_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart create mode 100644 packages/google_sign_in/google_sign_in_web/test/utils.dart diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md new file mode 100644 index 000000000000..f24bcf58874a --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.8.0 + +* Flutter for web initial release \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_web/LICENSE b/packages/google_sign_in/google_sign_in_web/LICENSE new file mode 100644 index 000000000000..4da9688730d1 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_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/google_sign_in/google_sign_in_web/README.md b/packages/google_sign_in/google_sign_in_web/README.md new file mode 100644 index 000000000000..182b21018aa4 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/README.md @@ -0,0 +1,80 @@ +# google_sign_in_web + +The web implementation of [google_sign_in](https://pub.dev/google_sign_in/google_sign_in) + +## Usage + +### Import the package +To use this plugin, follow the [plugin installation instructions](https://pub.dartlang.org/packages/google_sign_in#pub-pkg-tab-installing). + +Remember that for web plugins you need to depend both on the "native" version that provides the Dart interface that you'll use in your app), and the "web" version, that provides the implementation of the plugin for the web platform. + +This is what the above means to your `pubspec.yaml`: + +``` +... +dependencies: + ... + google_sign_in: ^4.0.14 + google_sign_in_web: ^0.8.0 + ... +... +``` + +### Web integration + +First, go through the instructions [here](https://developers.google.com/identity/sign-in/web/sign-in#before_you_begin) to create your Google Sign-In OAuth client ID. + +On your `web/index.html` file, add the following `meta` tag, somewhere in the +`head` of the document: + +``` + +``` + +Read the rest of the instructions if you need to add extra APIs (like Google People API). + + +### Use the plugin +Add the following import to your Dart code: + +```dart +import 'package:google_sign_in/google_sign_in.dart'; +``` + +Initialize GoogleSignIn with the scopes you want: + +```dart +GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'https://www.googleapis.com/auth/contacts.readonly', + ], +); +``` +[Full list of available scopes](https://developers.google.com/identity/protocols/googlescopes). + +You can now use the `GoogleSignIn` class to authenticate in your Dart code, e.g. + +```dart +Future _handleSignIn() async { + try { + await _googleSignIn.signIn(); + } catch (error) { + print(error); + } +} +``` + +## Example + +Find the example wiring in the [Google sign-in example application](https://github.com/flutter/plugins/blob/master/packages/google_sign_in/google_sign_in/example/lib/main.dart). + +## API details + +See the [google_sign_in.dart](https://github.com/flutter/plugins/blob/master/packages/google_sign_in/google_sign_in/lib/google_sign_in.dart) for more API details. + +## 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/google_sign_in_web/analysis_options.yaml b/packages/google_sign_in/google_sign_in_web/analysis_options.yaml new file mode 100644 index 000000000000..6ff44d022af6 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/analysis_options.yaml @@ -0,0 +1,5 @@ +# Exclude auto-generated files from analysis/linting +analyzer: + exclude: + - "lib/src/generated/**" + \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart new file mode 100644 index 000000000000..c7889c9107e4 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/google_sign_in_web.dart @@ -0,0 +1,151 @@ +// 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. + +import 'dart:async'; +import 'dart:html' as html; + +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:js/js.dart'; +import 'package:meta/meta.dart'; + +import 'src/generated/gapiauth2.dart' as auth2; +// TODO: Remove once this lands https://github.com/dart-lang/language/issues/671 +import 'src/generated/gapiauth2.dart' show GoogleAuthExtensions; +import 'src/load_gapi.dart' as gapi; +import 'src/utils.dart' show gapiUserToPluginUserData; + +const String _kClientIdMetaSelector = 'meta[name=google-signin-client_id]'; +const String _kClientIdAttributeName = 'content'; + +@visibleForTesting +String gapiUrl = 'https://apis.google.com/js/platform.js'; + +/// Implementation of the google_sign_in plugin for Web +class GoogleSignInPlugin extends GoogleSignInPlatform { + GoogleSignInPlugin() { + _autoDetectedClientId = html + .querySelector(_kClientIdMetaSelector) + ?.getAttribute(_kClientIdAttributeName); + + _isGapiInitialized = gapi.inject(gapiUrl).then((_) => gapi.init()); + } + + Future _isGapiInitialized; + + @visibleForTesting + Future get initialized => _isGapiInitialized; + + String _autoDetectedClientId; + FutureOr _lastSeenUser; + + static void registerWith(Registrar registrar) { + GoogleSignInPlatform.instance = GoogleSignInPlugin(); + } + + @override + Future init( + {@required String hostedDomain, + List scopes = const [], + SignInOption signInOption = SignInOption.standard, + String clientId}) async { + final String appClientId = clientId ?? _autoDetectedClientId; + assert( + appClientId != null, + 'ClientID not set. Either set it on a ' + ' tag,' + ' or pass clientId when calling init()'); + + assert( + !scopes.any((String scope) => scope.contains(' ')), + 'OAuth 2.0 Scopes for Google APIs can\'t contain spaces.' + 'Check https://developers.google.com/identity/protocols/googlescopes ' + 'for a list of valid OAuth 2.0 scopes.'); + + await initialized; + + final auth2.GoogleAuth auth = auth2.init(auth2.ClientConfig( + hosted_domain: hostedDomain, + // The js lib wants a space-separated list of values + scope: scopes.join(' '), + client_id: appClientId, + )); + + // Subscribe to changes in the auth instance returned by init, + // and cache the _lastSeenUser as we get notified of new values. + final Completer initUserCompleter = + Completer(); + + auth.currentUser.listen(allowInterop((auth2.GoogleUser nextUser) { + if (!initUserCompleter.isCompleted) { + initUserCompleter.complete(nextUser); + } else { + _lastSeenUser = nextUser; + } + })); + _lastSeenUser = initUserCompleter.future; + + return null; + } + + @override + Future signInSilently() async { + await initialized; + + return gapiUserToPluginUserData(await _lastSeenUser); + } + + @override + Future signIn() async { + await initialized; + + return gapiUserToPluginUserData(await auth2.getAuthInstance().signIn()); + } + + @override + Future getTokens( + {@required String email, bool shouldRecoverAuth}) async { + await initialized; + + final auth2.GoogleUser currentUser = + auth2.getAuthInstance()?.currentUser?.get(); + final auth2.AuthResponse response = currentUser.getAuthResponse(); + + return GoogleSignInTokenData( + idToken: response.id_token, accessToken: response.access_token); + } + + @override + Future signOut() async { + await initialized; + + return auth2.getAuthInstance().signOut(); + } + + @override + Future disconnect() async { + await initialized; + + final auth2.GoogleUser currentUser = + auth2.getAuthInstance()?.currentUser?.get(); + return currentUser.disconnect(); + } + + @override + Future isSignedIn() async { + await initialized; + + final auth2.GoogleUser currentUser = + auth2.getAuthInstance()?.currentUser?.get(); + return currentUser.isSignedIn(); + } + + @override + Future clearAuthCache({String token}) async { + await initialized; + + _lastSeenUser = null; + return auth2.getAuthInstance().disconnect(); + } +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart new file mode 100644 index 000000000000..c64d93be223a --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapi.dart @@ -0,0 +1,459 @@ +// 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. + +@JS() +library gapi; + +import "package:js/js.dart"; +import "package:js/js_util.dart" show promiseToFuture; + +/// Type definitions for Google API Client +/// Project: https://github.com/google/google-api-javascript-client +/// Definitions by: Frank M , grant +/// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/// TypeScript Version: 2.3 + +/// The OAuth 2.0 token object represents the OAuth 2.0 token and any associated data. +@anonymous +@JS() +abstract class GoogleApiOAuth2TokenObject { + /// The OAuth 2.0 token. Only present in successful responses + external String get access_token; + external set access_token(String v); + + /// Details about the error. Only present in error responses + external String get error; + external set error(String v); + + /// The duration, in seconds, the token is valid for. Only present in successful responses + external String get expires_in; + external set expires_in(String v); + external GoogleApiOAuth2TokenSessionState get session_state; + external set session_state(GoogleApiOAuth2TokenSessionState v); + + /// The Google API scopes related to this token + external String get state; + external set state(String v); + external factory GoogleApiOAuth2TokenObject( + {String access_token, + String error, + String expires_in, + GoogleApiOAuth2TokenSessionState session_state, + String state}); +} + +@anonymous +@JS() +abstract class GoogleApiOAuth2TokenSessionState { + external dynamic /*{ + authuser: string, + }*/ + get extraQueryParams; + external set extraQueryParams( + dynamic + /*{ + authuser: string, + }*/ + v); + external factory GoogleApiOAuth2TokenSessionState( + {dynamic + /*{ + authuser: string, + }*/ + extraQueryParams}); +} + +/// Fix for #8215 +/// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/8215 +/// Usage example: +/// https://developers.google.com/identity/sign-in/web/session-state + +// Module gapi +typedef void LoadCallback( + [dynamic args1, + dynamic args2, + dynamic args3, + dynamic args4, + dynamic args5]); + +@anonymous +@JS() +abstract class LoadConfig { + external LoadCallback get callback; + external set callback(LoadCallback v); + external Function get onerror; + external set onerror(Function v); + external num get timeout; + external set timeout(num v); + external Function get ontimeout; + external set ontimeout(Function v); + external factory LoadConfig( + {LoadCallback callback, + Function onerror, + num timeout, + Function ontimeout}); +} + +/*type CallbackOrConfig = LoadConfig | LoadCallback;*/ +/// Pragmatically initialize gapi class member. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiloadlibraries-callbackorconfig +@JS("gapi.load") +external void load( + String apiName, dynamic /*LoadConfig|LoadCallback*/ callback); +// End module gapi + +// Module gapi.auth +/// Initiates the OAuth 2.0 authorization process. The browser displays a popup window prompting the user authenticate and authorize. After the user authorizes, the popup closes and the callback function fires. +@JS("gapi.auth.authorize") +external void authorize( + dynamic + /*{ + /** + * The application's client ID. + */ + client_id?: string; + /** + * If true, then login uses "immediate mode", which means that the token is refreshed behind the scenes, and no UI is shown to the user. + */ + immediate?: boolean; + /** + * The OAuth 2.0 response type property. Default: token + */ + response_type?: string; + /** + * The auth scope or scopes to authorize. Auth scopes for individual APIs can be found in their documentation. + */ + scope?: any; + /** + * The user to sign in as. -1 to toggle a multi-account chooser, 0 to default to the user's current account, and 1 to automatically sign in if the user is signed into Google Plus. + */ + authuser?: number; + }*/ + params, + dynamic callback(GoogleApiOAuth2TokenObject token)); + +/// Initializes the authorization feature. Call this when the client loads to prevent popup blockers from blocking the auth window on gapi.auth.authorize calls. +@JS("gapi.auth.init") +external void init(dynamic callback()); + +/// Retrieves the OAuth 2.0 token for the application. +@JS("gapi.auth.getToken") +external GoogleApiOAuth2TokenObject getToken(); + +/// Sets the OAuth 2.0 token for the application. +@JS("gapi.auth.setToken") +external void setToken(GoogleApiOAuth2TokenObject token); + +/// Initiates the client-side Google+ Sign-In OAuth 2.0 flow. +/// When the method is called, the OAuth 2.0 authorization dialog is displayed to the user and when they accept, the callback function is called. +@JS("gapi.auth.signIn") +external void signIn( + dynamic + /*{ + /** + * Your OAuth 2.0 client ID that you obtained from the Google Developers Console. + */ + clientid?: string; + /** + * Directs the sign-in button to store user and session information in a session cookie and HTML5 session storage on the user's client for the purpose of minimizing HTTP traffic and distinguishing between multiple Google accounts a user might be signed into. + */ + cookiepolicy?: string; + /** + * A function in the global namespace, which is called when the sign-in button is rendered and also called after a sign-in flow completes. + */ + callback?: () => void; + /** + * If true, all previously granted scopes remain granted in each incremental request, for incremental authorization. The default value true is correct for most use cases; use false only if employing delegated auth, where you pass the bearer token to a less-trusted component with lower programmatic authority. + */ + includegrantedscopes?: boolean; + /** + * If your app will write moments, list the full URI of the types of moments that you intend to write. + */ + requestvisibleactions?: any; + /** + * The OAuth 2.0 scopes for the APIs that you would like to use as a space-delimited list. + */ + scope?: any; + /** + * If you have an Android app, you can drive automatic Android downloads from your web sign-in flow. + */ + apppackagename?: string; + }*/ + params); + +/// Signs a user out of your app without logging the user out of Google. This method will only work when the user is signed in with Google+ Sign-In. +@JS("gapi.auth.signOut") +external void signOut(); +// End module gapi.auth + +// Module gapi.client +@anonymous +@JS() +abstract class RequestOptions { + /// The URL to handle the request + external String get path; + external set path(String v); + + /// The HTTP request method to use. Default is GET + external String get method; + external set method(String v); + + /// URL params in key-value pair form + external dynamic get params; + external set params(dynamic v); + + /// Additional HTTP request headers + external dynamic get headers; + external set headers(dynamic v); + + /// The HTTP request body (applies to PUT or POST). + external dynamic get body; + external set body(dynamic v); + + /// If supplied, the request is executed immediately and no gapi.client.HttpRequest object is returned + external dynamic Function() get callback; + external set callback(dynamic Function() v); + external factory RequestOptions( + {String path, + String method, + dynamic params, + dynamic headers, + dynamic body, + dynamic Function() callback}); +} + +@anonymous +@JS() +abstract class _RequestOptions { + @JS("gapi.client.init") + external Promise client_init( + dynamic + /*{ + /** + * The API Key to use. + */ + apiKey?: string; + /** + * An array of discovery doc URLs or discovery doc JSON objects. + */ + discoveryDocs?: string[]; + /** + * The app's client ID, found and created in the Google Developers Console. + */ + clientId?: string; + /** + * The scopes to request, as a space-delimited string. + */ + scope?: string, + + hosted_domain?: string; + }*/ + args); +} + +extension RequestOptionsExtensions on RequestOptions {} + +@anonymous +@JS() +abstract class TokenObject { + /// The access token to use in requests. + external String get access_token; + external set access_token(String v); + external factory TokenObject({String access_token}); +} + +/// Creates a HTTP request for making RESTful requests. +/// An object encapsulating the various arguments for this method. +@JS("gapi.client.request") +external HttpRequest request(RequestOptions args); + +/// Creates an RPC Request directly. The method name and version identify the method to be executed and the RPC params are provided upon RPC creation. +@JS("gapi.client.rpcRequest") +external RpcRequest rpcRequest(String method, + [String version, dynamic rpcParams]); + +/// Sets the API key for the application. +@JS("gapi.client.setApiKey") +external void setApiKey(String apiKey); + +/// Retrieves the OAuth 2.0 token for the application. +@JS("gapi.client.getToken") +external GoogleApiOAuth2TokenObject client_getToken(); + +/// Sets the authentication token to use in requests. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiclientsettokentokenobject +@JS("gapi.client.setToken") +external void client_setToken(TokenObject /*TokenObject|Null*/ token); + +@anonymous +@JS() +abstract class HttpRequestFulfilled { + external T get result; + external set result(T v); + external String get body; + external set body(String v); + external List get headers; + external set headers(List v); + external num get status; + external set status(num v); + external String get statusText; + external set statusText(String v); + external factory HttpRequestFulfilled( + {T result, + String body, + List headers, + num status, + String statusText}); +} + +@anonymous +@JS() +abstract class _HttpRequestFulfilled { + /*external Promise client_load(String name, String version);*/ + /*external void client_load(String name, String version, dynamic callback(), + [String url]); +*/ + @JS("gapi.client.load") + external dynamic /*Promise|void*/ client_load( + String name, String version, + [dynamic callback(), String url]); +} + +extension HttpRequestFulfilledExtensions on HttpRequestFulfilled {} + +@anonymous +@JS() +abstract class HttpRequestRejected { + external dynamic /*dynamic|bool*/ get result; + external set result(dynamic /*dynamic|bool*/ v); + external String get body; + external set body(String v); + external List get headers; + external set headers(List v); + external num get status; + external set status(num v); + external String get statusText; + external set statusText(String v); + external factory HttpRequestRejected( + {dynamic /*dynamic|bool*/ result, + String body, + List headers, + num status, + String statusText}); +} + +/// HttpRequest supports promises. +/// See Google API Client JavaScript Using Promises https://developers.google.com/api-client-library/javascript/features/promises +@JS("gapi.client.HttpRequestPromise") +class HttpRequestPromise { + // @Ignore + HttpRequestPromise.fakeConstructor$(); +} + +@JS("gapi.client.HttpRequestPromise") +abstract class _HttpRequestPromise { + /// Taken and adapted from https://github.com/Microsoft/TypeScript/blob/v2.3.1/lib/lib.es5.d.ts#L1343 + external Promise then/**/( + [dynamic /*TResult1|PromiseLike Function(HttpRequestFulfilled)|dynamic|Null*/ onfulfilled, + dynamic /*TResult2|PromiseLike Function(HttpRequestRejected)|dynamic|Null*/ onrejected, + dynamic opt_context]); +} + +extension HttpRequestPromiseExtensions on HttpRequestPromise { + Future then( + [dynamic /*TResult1|PromiseLike Function(HttpRequestFulfilled)|dynamic|Null*/ onfulfilled, + dynamic /*TResult2|PromiseLike Function(HttpRequestRejected)|dynamic|Null*/ onrejected, + dynamic opt_context]) { + final Object t = this; + final _HttpRequestPromise tt = t; + return promiseToFuture(tt.then(onfulfilled, onrejected, opt_context)); + } +} + +/// An object encapsulating an HTTP request. This object is not instantiated directly, rather it is returned by gapi.client.request. +@JS("gapi.client.HttpRequest") +class HttpRequest extends HttpRequestPromise { + // @Ignore + HttpRequest.fakeConstructor$() : super.fakeConstructor$(); + + /// Executes the request and runs the supplied callback on response. + external void execute( + dynamic callback( + + /// contains the response parsed as JSON. If the response is not JSON, this field will be false. + T jsonResp, + + /// is the HTTP response. It is JSON, and can be parsed to an object + dynamic + /*{ + body: string; + headers: any[]; + status: number; + statusText: string; + }*/ + rawResp)); +} + +/// Represents an HTTP Batch operation. Individual HTTP requests are added with the add method and the batch is executed using execute. +@JS("gapi.client.HttpBatch") +class HttpBatch { + // @Ignore + HttpBatch.fakeConstructor$(); + + /// Adds a gapi.client.HttpRequest to the batch. + external void add(HttpRequest httpRequest, + [dynamic + /*{ + /** + * Identifies the response for this request in the map of batch responses. If one is not provided, the system generates a random ID. + */ + id: string; + callback: ( + /** + * is the response for this request only. Its format is defined by the API method being called. + */ + individualResponse: any, + /** + * is the raw batch ID-response map as a string. It contains all responses to all requests in the batch. + */ + rawBatchResponse: any + ) => any + }*/ + opt_params]); + + /// Executes all requests in the batch. The supplied callback is executed on success or failure. + external void execute( + dynamic callback( + + /// is an ID-response map of each requests response. + dynamic responseMap, + + /// is the same response, but as an unparsed JSON-string. + String rawBatchResponse)); +} + +/// Similar to gapi.client.HttpRequest except this object encapsulates requests generated by registered methods. +@JS("gapi.client.RpcRequest") +class RpcRequest { + // @Ignore + RpcRequest.fakeConstructor$(); + + /// Executes the request and runs the supplied callback with the response. + external void callback( + void callback( + + /// contains the response parsed as JSON. If the response is not JSON, this field will be false. + dynamic jsonResp, + + /// is the same as jsonResp, except it is a raw string that has not been parsed. It is typically used when the response is not JSON. + String rawResp)); +} + +// End module gapi.client +@JS() +abstract class Promise { + external factory Promise( + void executor(void resolve(T result), Function reject)); + external Promise then(void onFulfilled(T result), [Function onRejected]); +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart new file mode 100644 index 000000000000..5071f93e6e30 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/generated/gapiauth2.dart @@ -0,0 +1,464 @@ +// 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. + +@JS() +library gapiauth2; + +import "package:js/js.dart"; +import "package:js/js_util.dart" show promiseToFuture; + +/// Type definitions for non-npm package Google Sign-In API 0.0 +/// Project: https://developers.google.com/identity/sign-in/web/ +/// Definitions by: Derek Lawless +/// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +/// TypeScript Version: 2.3 + +/// + +// Module gapi.auth2 +/// GoogleAuth is a singleton class that provides methods to allow the user to sign in with a Google account, +/// get the user's current sign-in status, get specific data from the user's Google profile, +/// request additional scopes, and sign out from the current account. +@JS("gapi.auth2.GoogleAuth") +class GoogleAuth { + // @Ignore + GoogleAuth.fakeConstructor$(); + external IsSignedIn get isSignedIn; + external set isSignedIn(IsSignedIn v); + external CurrentUser get currentUser; + external set currentUser(CurrentUser v); + + /// Calls the onInit function when the GoogleAuth object is fully initialized, or calls the onFailure function if + /// initialization fails. + external dynamic then(dynamic onInit(GoogleAuth googleAuth), + [dynamic onFailure(dynamic /*{error: string, details: string}*/ reason)]); + + /// Signs out all accounts from the application. + external dynamic signOut(); + + /// Revokes all of the scopes that the user granted. + external dynamic disconnect(); + + /// Attaches the sign-in flow to the specified container's click handler. + external dynamic attachClickHandler( + dynamic container, + SigninOptions options, + dynamic onsuccess(GoogleUser googleUser), + dynamic onfailure(String reason)); +} + +@anonymous +@JS() +abstract class _GoogleAuth { + external Promise signIn( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]); + external Promise grantOfflineAccess( + [OfflineAccessOptions options]); +} + +extension GoogleAuthExtensions on GoogleAuth { + Future signIn( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]) { + final Object t = this; + final _GoogleAuth tt = t; + return promiseToFuture(tt.signIn(options)); + } + + Future grantOfflineAccess( + [OfflineAccessOptions options]) { + final Object t = this; + final _GoogleAuth tt = t; + return promiseToFuture(tt.grantOfflineAccess(options)); + } +} + +@anonymous +@JS() +abstract class IsSignedIn { + /// Returns whether the current user is currently signed in. + external bool get(); + + /// Listen for changes in the current user's sign-in state. + external void listen(dynamic listener(bool signedIn)); +} + +@anonymous +@JS() +abstract class CurrentUser { + /// Returns a GoogleUser object that represents the current user. Note that in a newly-initialized + /// GoogleAuth instance, the current user has not been set. Use the currentUser.listen() method or the + /// GoogleAuth.then() to get an initialized GoogleAuth instance. + external GoogleUser get(); + + /// Listen for changes in currentUser. + external void listen(dynamic listener(GoogleUser user)); +} + +@anonymous +@JS() +abstract class SigninOptions { + /// The package name of the Android app to install over the air. + /// See Android app installs from your web site: + /// https://developers.google.com/identity/sign-in/web/android-app-installs + external String get app_package_name; + external set app_package_name(String v); + + /// Fetch users' basic profile information when they sign in. + /// Adds 'profile', 'email' and 'openid' to the requested scopes. + /// True if unspecified. + external bool get fetch_basic_profile; + external set fetch_basic_profile(bool v); + + /// Specifies whether to prompt the user for re-authentication. + /// See OpenID Connect Request Parameters: + /// https://openid.net/specs/openid-connect-basic-1_0.html#RequestParameters + external String get prompt; + external set prompt(String v); + + /// The scopes to request, as a space-delimited string. + /// Optional if fetch_basic_profile is not set to false. + external String get scope; + external set scope(String v); + + /// The UX mode to use for the sign-in flow. + /// By default, it will open the consent flow in a popup. + external String /*'popup'|'redirect'*/ get ux_mode; + external set ux_mode(String /*'popup'|'redirect'*/ v); + + /// If using ux_mode='redirect', this parameter allows you to override the default redirect_uri that will be used at the end of the consent flow. + /// The default redirect_uri is the current URL stripped of query parameters and hash fragment. + external String get redirect_uri; + external set redirect_uri(String v); + external factory SigninOptions( + {String app_package_name, + bool fetch_basic_profile, + String prompt, + String scope, + String /*'popup'|'redirect'*/ ux_mode, + String redirect_uri}); +} + +/// Definitions by: John +/// Interface that represents the different configuration parameters for the GoogleAuth.grantOfflineAccess(options) method. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2offlineaccessoptions +@anonymous +@JS() +abstract class OfflineAccessOptions { + external String get scope; + external set scope(String v); + external String /*'select_account'|'consent'*/ get prompt; + external set prompt(String /*'select_account'|'consent'*/ v); + external String get app_package_name; + external set app_package_name(String v); + external factory OfflineAccessOptions( + {String scope, + String /*'select_account'|'consent'*/ prompt, + String app_package_name}); +} + +/// Interface that represents the different configuration parameters for the gapi.auth2.init method. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2clientconfig +@anonymous +@JS() +abstract class ClientConfig { + /// The app's client ID, found and created in the Google Developers Console. + external String get client_id; + external set client_id(String v); + + /// The domains for which to create sign-in cookies. Either a URI, single_host_origin, or none. + /// Defaults to single_host_origin if unspecified. + external String get cookie_policy; + external set cookie_policy(String v); + + /// The scopes to request, as a space-delimited string. Optional if fetch_basic_profile is not set to false. + external String get scope; + external set scope(String v); + + /// Fetch users' basic profile information when they sign in. Adds 'profile' and 'email' to the requested scopes. True if unspecified. + external bool get fetch_basic_profile; + external set fetch_basic_profile(bool v); + + /// The Google Apps domain to which users must belong to sign in. This is susceptible to modification by clients, + /// so be sure to verify the hosted domain property of the returned user. Use GoogleUser.getHostedDomain() on the client, + /// and the hd claim in the ID Token on the server to verify the domain is what you expected. + external String get hosted_domain; + external set hosted_domain(String v); + + /// Used only for OpenID 2.0 client migration. Set to the value of the realm that you are currently using for OpenID 2.0, + /// as described in OpenID 2.0 (Migration). + external String get openid_realm; + external set openid_realm(String v); + + /// The UX mode to use for the sign-in flow. + /// By default, it will open the consent flow in a popup. + external String /*'popup'|'redirect'*/ get ux_mode; + external set ux_mode(String /*'popup'|'redirect'*/ v); + + /// If using ux_mode='redirect', this parameter allows you to override the default redirect_uri that will be used at the end of the consent flow. + /// The default redirect_uri is the current URL stripped of query parameters and hash fragment. + external String get redirect_uri; + external set redirect_uri(String v); + external factory ClientConfig( + {String client_id, + String cookie_policy, + String scope, + bool fetch_basic_profile, + String hosted_domain, + String openid_realm, + String /*'popup'|'redirect'*/ ux_mode, + String redirect_uri}); +} + +@JS("gapi.auth2.SigninOptionsBuilder") +class SigninOptionsBuilder { + // @Ignore + SigninOptionsBuilder.fakeConstructor$(); + external dynamic setAppPackageName(String name); + external dynamic setFetchBasicProfile(bool fetch); + external dynamic setPrompt(String prompt); + external dynamic setScope(String scope); +} + +@anonymous +@JS() +abstract class BasicProfile { + external String getId(); + external String getName(); + external String getGivenName(); + external String getFamilyName(); + external String getImageUrl(); + external String getEmail(); +} + +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authresponse +@anonymous +@JS() +abstract class AuthResponse { + external String get access_token; + external set access_token(String v); + external String get id_token; + external set id_token(String v); + external String get login_hint; + external set login_hint(String v); + external String get scope; + external set scope(String v); + external num get expires_in; + external set expires_in(num v); + external num get first_issued_at; + external set first_issued_at(num v); + external num get expires_at; + external set expires_at(num v); + external factory AuthResponse( + {String access_token, + String id_token, + String login_hint, + String scope, + num expires_in, + num first_issued_at, + num expires_at}); +} + +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeconfig +@anonymous +@JS() +abstract class AuthorizeConfig { + external String get client_id; + external set client_id(String v); + external String get scope; + external set scope(String v); + external String get response_type; + external set response_type(String v); + external String get prompt; + external set prompt(String v); + external String get cookie_policy; + external set cookie_policy(String v); + external String get hosted_domain; + external set hosted_domain(String v); + external String get login_hint; + external set login_hint(String v); + external String get app_package_name; + external set app_package_name(String v); + external String get openid_realm; + external set openid_realm(String v); + external bool get include_granted_scopes; + external set include_granted_scopes(bool v); + external factory AuthorizeConfig( + {String client_id, + String scope, + String response_type, + String prompt, + String cookie_policy, + String hosted_domain, + String login_hint, + String app_package_name, + String openid_realm, + bool include_granted_scopes}); +} + +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeresponse +@anonymous +@JS() +abstract class AuthorizeResponse { + external String get access_token; + external set access_token(String v); + external String get id_token; + external set id_token(String v); + external String get code; + external set code(String v); + external String get scope; + external set scope(String v); + external num get expires_in; + external set expires_in(num v); + external num get first_issued_at; + external set first_issued_at(num v); + external num get expires_at; + external set expires_at(num v); + external String get error; + external set error(String v); + external String get error_subtype; + external set error_subtype(String v); + external factory AuthorizeResponse( + {String access_token, + String id_token, + String code, + String scope, + num expires_in, + num first_issued_at, + num expires_at, + String error, + String error_subtype}); +} + +/// A GoogleUser object represents one user account. +@anonymous +@JS() +abstract class GoogleUser { + /// Get the user's unique ID string. + external String getId(); + + /// Returns true if the user is signed in. + external bool isSignedIn(); + + /// Get the user's Google Apps domain if the user signed in with a Google Apps account. + external String getHostedDomain(); + + /// Get the scopes that the user granted as a space-delimited string. + external String getGrantedScopes(); + + /// Get the user's basic profile information. + external BasicProfile getBasicProfile(); + + /// Get the response object from the user's auth session. + external AuthResponse getAuthResponse([bool includeAuthorizationData]); + + /// Returns true if the user granted the specified scopes. + external bool hasGrantedScopes(String scopes); + + /// Signs in the user. Use this method to request additional scopes for incremental + /// authorization or to sign in a user after the user has signed out. + /// When you use GoogleUser.signIn(), the sign-in flow skips the account chooser step. + /// See GoogleAuth.signIn(). + external dynamic signIn( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]); + + /// See GoogleUser.signIn() + external dynamic grant( + [dynamic /*SigninOptions|SigninOptionsBuilder*/ options]); + + /// Get permission from the user to access the specified scopes offline. + /// When you use GoogleUser.grantOfflineAccess(), the sign-in flow skips the account chooser step. + /// See GoogleUser.grantOfflineAccess(). + external void grantOfflineAccess(String scopes); + + /// Revokes all of the scopes that the user granted. + external void disconnect(); +} + +@anonymous +@JS() +abstract class _GoogleUser { + external Promise reloadAuthResponse(); +} + +extension GoogleUserExtensions on GoogleUser { + Future reloadAuthResponse() { + final Object t = this; + final _GoogleUser tt = t; + return promiseToFuture(tt.reloadAuthResponse()); + } +} + +/// Initializes the GoogleAuth object. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2initparams +@JS("gapi.auth2.init") +external GoogleAuth init(ClientConfig params); + +/// Returns the GoogleAuth object. You must initialize the GoogleAuth object with gapi.auth2.init() before calling this method. +@JS("gapi.auth2.getAuthInstance") +external GoogleAuth getAuthInstance(); + +/// Performs a one time OAuth 2.0 authorization. +/// Reference: https://developers.google.com/api-client-library/javascript/reference/referencedocs#gapiauth2authorizeparams-callback +@JS("gapi.auth2.authorize") +external void authorize( + AuthorizeConfig params, void callback(AuthorizeResponse response)); +// End module gapi.auth2 + +// Module gapi.signin2 +@JS("gapi.signin2.render") +external void render( + dynamic id, + dynamic + /*{ + /** + * The auth scope or scopes to authorize. Auth scopes for individual APIs can be found in their documentation. + */ + scope?: string; + + /** + * The width of the button in pixels (default: 120). + */ + width?: number; + + /** + * The height of the button in pixels (default: 36). + */ + height?: number; + + /** + * Display long labels such as "Sign in with Google" rather than "Sign in" (default: false). + */ + longtitle?: boolean; + + /** + * The color theme of the button: either light or dark (default: light). + */ + theme?: string; + + /** + * The callback function to call when a user successfully signs in (default: none). + */ + onsuccess?(user: auth2.GoogleUser): void; + + /** + * The callback function to call when sign-in fails (default: none). + */ + onfailure?(reason: { error: string }): void; + + /** + * The package name of the Android app to install over the air. See + * Android app installs from your web site. + * Optional. (default: none) + */ + app_package_name?: string; + }*/ + options); + +// End module gapi.signin2 +@JS() +abstract class Promise { + external factory Promise( + void executor(void resolve(T result), Function reject)); + external Promise then(void onFulfilled(T result), [Function onRejected]); +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart new file mode 100644 index 000000000000..8273f39630f9 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/load_gapi.dart @@ -0,0 +1,55 @@ +// 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. + +@JS() +library gapi_onload; + +import 'dart:async'; + +import 'package:js/js.dart'; +import 'package:meta/meta.dart'; + +import 'generated/gapi.dart' as gapi; +import 'utils.dart' show injectJSLibraries; + +@JS() +external set gapiOnloadCallback(Function callback); + +// This name must match the external setter above +@visibleForTesting +const String kGapiOnloadCallbackFunctionName = "gapiOnloadCallback"; +String _addOnloadToScript(String url) => url.startsWith('data:') + ? url + : '$url?onload=$kGapiOnloadCallbackFunctionName'; + +/// Injects the GAPI library by its [url], and other additional [libraries]. +/// +/// GAPI has an onload API where it'll call a callback when it's ready, JSONP style. +Future inject(String url, {List libraries = const []}) { + // Inject the GAPI library, and configure the onload global + final Completer gapiOnLoad = Completer(); + gapiOnloadCallback = allowInterop(() { + // Funnel the GAPI onload to a Dart future + gapiOnLoad.complete(); + }); + + // Attach the onload callback to the main url + final List allLibraries = [_addOnloadToScript(url)] + ..addAll(libraries); + + return Future.wait( + >[injectJSLibraries(allLibraries), gapiOnLoad.future]); +} + +/// Initialize the global gapi object so 'auth2' can be used. +/// Returns a promise that resolves when 'auth2' is ready. +Future init() { + final Completer gapiLoadCompleter = Completer(); + gapi.load('auth2', allowInterop(() { + gapiLoadCompleter.complete(); + })); + + // After this resolves, we can use gapi.auth2! + return gapiLoadCompleter.future; +} diff --git a/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart new file mode 100644 index 000000000000..c8ca8007c1bb --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/lib/src/utils.dart @@ -0,0 +1,42 @@ +// 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. + +import 'dart:async'; +import 'dart:html' as html; + +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; + +import 'generated/gapiauth2.dart' as auth2; + +/// Injects a bunch of libraries in the and returns a +/// Future that resolves when all load. +Future injectJSLibraries(List libraries, + {html.HtmlElement target /*, Duration timeout */}) { + final List> loading = >[]; + final List tags = []; + + libraries.forEach((String library) { + final html.ScriptElement script = html.ScriptElement() + ..async = true + ..defer = true + ..src = library; + // TODO add a timeout race to fail this future + loading.add(script.onLoad.first); + tags.add(script); + }); + (target ?? html.querySelector('head')).children.addAll(tags); + return Future.wait(loading); +} + +GoogleSignInUserData gapiUserToPluginUserData(auth2.GoogleUser currentUser) { + assert(currentUser != null); + final auth2.BasicProfile profile = currentUser.getBasicProfile(); + return GoogleSignInUserData( + displayName: profile?.getName(), + email: profile?.getEmail(), + id: profile?.getId(), + photoUrl: profile?.getImageUrl(), + idToken: currentUser.getAuthResponse()?.id_token, + ); +} diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml new file mode 100644 index 000000000000..0c33fe402e83 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -0,0 +1,31 @@ +name: google_sign_in_web +description: Flutter plugin for Google Sign-In, a secure authentication system + for signing in with a Google account on Android, iOS and Web. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web +version: 0.8.0 + +flutter: + plugin: + platforms: + web: + pluginClass: GoogleSignInPlugin + fileName: google_sign_in_web.dart + +dependencies: + google_sign_in_platform_interface: ^1.0.0 + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + meta: ^1.1.7 + js: ^0.6.1 + +dev_dependencies: + flutter_test: + sdk: flutter + google_sign_in: ^4.0.14 + +environment: + sdk: ">=2.6.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart new file mode 100644 index 000000000000..b6eb60754b49 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/auth2_test.dart @@ -0,0 +1,77 @@ +// 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('browser') + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; +import 'package:google_sign_in_web/google_sign_in_web.dart'; +import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; +import 'utils.dart'; + +void main() { + GoogleSignInTokenData expectedTokenData = + GoogleSignInTokenData(idToken: '70k3n', accessToken: 'access_70k3n'); + + GoogleSignInUserData expectedUserData = GoogleSignInUserData( + displayName: 'Foo Bar', + email: 'foo@example.com', + id: '123', + photoUrl: 'http://example.com/img.jpg', + idToken: expectedTokenData.idToken, + ); + + // The pre-configured use case for the instances of the plugin in this test + gapiUrl = toBase64Url(gapi_mocks.auth2InitSuccess(expectedUserData)); + + GoogleSignInPlugin plugin; + + setUp(() async { + plugin = GoogleSignInPlugin(); + await plugin.initialized; + }); + + test('Init requires clientId', () async { + expect(plugin.init(hostedDomain: ''), throwsAssertionError); + }); + + test('Init doesn\'t accept spaces in scopes', () async { + expect( + plugin.init( + hostedDomain: '', + clientId: '', + scopes: ['scope with spaces'], + ), + throwsAssertionError); + }); + + group('Successful .init, then', () { + setUp(() async { + plugin.init( + hostedDomain: 'foo', + scopes: ['some', 'scope'], + clientId: '1234', + ); + }); + + test('signInSilently', () async { + GoogleSignInUserData actualUser = await plugin.signInSilently(); + + expect(actualUser, expectedUserData); + }); + + test('signIn', () async { + GoogleSignInUserData actualUser = await plugin.signIn(); + + expect(actualUser, expectedUserData); + }); + + test('getTokens', () async { + GoogleSignInTokenData actualToken = + await plugin.getTokens(email: expectedUserData.email); + + expect(actualToken, expectedTokenData); + }); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart new file mode 100644 index 000000000000..815dcdcfc645 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_load_test.dart @@ -0,0 +1,36 @@ +// 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('browser') + +import 'dart:html' as html; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_sign_in_web/google_sign_in_web.dart'; +import 'gapi_mocks/gapi_mocks.dart' as gapi_mocks; +import 'utils.dart'; + +void main() { + gapiUrl = toBase64Url(gapi_mocks.gapiInitSuccess()); + + test('Plugin is initialized after GAPI fully loads', () async { + expect( + html.querySelector('script[src^="data:"]'), + isNull, + reason: 'Mock script not present before instantiating the plugin', + ); + final GoogleSignInPlugin plugin = GoogleSignInPlugin(); + expect( + html.querySelector('script[src^="data:"]'), + isNotNull, + reason: 'Mock script should be injected', + ); + await plugin.initialized; + expect( + plugin.initialized, + completes, + reason: 'The plugin should complete the future once initialized.', + ); + }); +} diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart new file mode 100644 index 000000000000..d36cd5edf63a --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/gapi_mocks.dart @@ -0,0 +1,14 @@ +// 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. + +library gapi_mocks; + +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; + +import 'src/gapi.dart'; +import 'src/google_user.dart'; +import 'src/test_iife.dart'; + +part 'src/gapi_load.dart'; +part 'src/auth2_init.dart'; diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart new file mode 100644 index 000000000000..5484dc5b61bc --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/auth2_init.dart @@ -0,0 +1,42 @@ +// 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. + +part of gapi_mocks; + +// JS mock of a gapi.auth2, with a successfully identified user +String auth2InitSuccess(GoogleSignInUserData userData) => testIife(''' +${gapi()} + +var mockUser = ${googleUser(userData)}; + +function GapiAuth2() {} +GapiAuth2.prototype.init = function (initOptions) { + return { + currentUser: { + listen: (cb) => { + window.setTimeout(() => { + cb(mockUser); + }, 30); + } + } + } +}; + +GapiAuth2.prototype.getAuthInstance = function () { + return { + signIn: () => { + return new Promise((resolve, reject) => { + window.setTimeout(() => { + resolve(mockUser); + }, 30); + }); + }, + currentUser: { + get: () => mockUser, + }, + } +}; + +window.gapi.auth2 = new GapiAuth2(); +'''); diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart new file mode 100644 index 000000000000..42d9a8be262c --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi.dart @@ -0,0 +1,12 @@ +// 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. + +// The JS mock of the global gapi object +String gapi() => ''' +function Gapi() {}; +Gapi.prototype.load = function (script, cb) { + window.setTimeout(cb, 30); +}; +window.gapi = new Gapi(); +'''; diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart new file mode 100644 index 000000000000..f390500f6f4e --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/gapi_load.dart @@ -0,0 +1,8 @@ +// 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. + +part of gapi_mocks; + +// JS mock of a raw GAPI object. +String gapiInitSuccess() => testIife(gapi()); diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart new file mode 100644 index 000000000000..9f2b7b9bf6fa --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/google_user.dart @@ -0,0 +1,25 @@ +// 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. + +import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; + +// Creates the JS representation of some user data +String googleUser(GoogleSignInUserData data) => ''' +{ + getBasicProfile: () => { + return { + getName: () => '${data.displayName}', + getEmail: () => '${data.email}', + getId: () => '${data.id}', + getImageUrl: () => '${data.photoUrl}', + }; + }, + getAuthResponse: () => { + return { + id_token: '${data.idToken}', + access_token: 'access_${data.idToken}', + } + }, +} +'''; diff --git a/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart new file mode 100644 index 000000000000..43a7a044fc1b --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/gapi_mocks/src/test_iife.dart @@ -0,0 +1,15 @@ +// 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. + +import 'package:google_sign_in_web/src/load_gapi.dart' + show kGapiOnloadCallbackFunctionName; + +// Wraps some JS mock code in an IIFE that ends by calling the onLoad dart callback. +String testIife(String mock) => ''' +(function() { + $mock; + window['$kGapiOnloadCallbackFunctionName'](); +})(); +''' + .replaceAll(RegExp(r'\s{2,}'), ''); diff --git a/packages/google_sign_in/google_sign_in_web/test/utils.dart b/packages/google_sign_in/google_sign_in_web/test/utils.dart new file mode 100644 index 000000000000..5a6c8906682c --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/test/utils.dart @@ -0,0 +1,10 @@ +// 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. + +import 'dart:convert'; + +String toBase64Url(String contents) { + // Open the file + return 'data:text/javascript;base64,' + base64.encode(utf8.encode(contents)); +} From 8b1f2f014fb5d1c87427b3a7bc1f8c0d5b90eb87 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 19 Nov 2019 13:00:30 -0800 Subject: [PATCH 20/47] [path_provider] Add missing DartDocs (#2288) Also adds a lint to prevent future undocumented public APIs. --- packages/path_provider/CHANGELOG.md | 6 +++- packages/path_provider/analysis_options.yaml | 11 ++++++ packages/path_provider/example/lib/main.dart | 2 ++ packages/path_provider/lib/path_provider.dart | 36 +++++++++++++++++++ packages/path_provider/pubspec.yaml | 2 +- 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 packages/path_provider/analysis_options.yaml diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md index 92ee15cbd918..a8c91a2071f5 100644 --- a/packages/path_provider/CHANGELOG.md +++ b/packages/path_provider/CHANGELOG.md @@ -1,6 +1,10 @@ +## 1.4.3 + +* Add missing DartDocs and a lint to prevent further regressions. + ## 1.4.2 -* Update and migrate iOS example project by removing flutter_assets, change +* Update and migrate iOS example project by removing flutter_assets, change "English" to "en", remove extraneous xcconfigs, update to Xcode 11 build settings, remove ARCHS, and build pods as libraries instead of frameworks. diff --git a/packages/path_provider/analysis_options.yaml b/packages/path_provider/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/path_provider/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/path_provider/example/lib/main.dart b/packages/path_provider/example/lib/main.dart index ea12999bf415..ce496e9d4e63 100644 --- a/packages/path_provider/example/lib/main.dart +++ b/packages/path_provider/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 'dart:io'; diff --git a/packages/path_provider/lib/path_provider.dart b/packages/path_provider/lib/path_provider.dart index 3840121c8c4b..52aceb786477 100644 --- a/packages/path_provider/lib/path_provider.dart +++ b/packages/path_provider/lib/path_provider.dart @@ -14,6 +14,8 @@ const MethodChannel _channel = Platform _platform = const LocalPlatform(); +/// This API is only exposed for the unit tests. It should not be used by +/// any code outside of the plugin itself. @visibleForTesting void setMockPathProviderPlatform(Platform platform) { _platform = platform; @@ -141,15 +143,49 @@ Future> getExternalCacheDirectories() async { /// /// https://developer.android.com/reference/android/os/Environment.html#fields_1 enum StorageDirectory { + /// Contains audio files that should be treated as music. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MUSIC. music, + + /// Contains audio files that should be treated as podcasts. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_PODCASTS. podcasts, + + /// Contains audio files that should be treated as ringtones. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_RINGTONES. ringtones, + + /// Contains audio files that should be treated as alarm sounds. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_ALARMS. alarms, + + /// Contains audio files that should be treated as notification sounds. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_NOTIFICATIONS. notifications, + + /// Contains images. See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_PICTURES. pictures, + + /// Contains movies. See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_MOVIES. movies, + + /// Contains files of any type that have been downloaded by the user. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_DOWNLOADS. downloads, + + /// Used to hold both pictures and videos when the device filesystem is + /// treated like a camera's. + /// + /// See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_DCIM. dcim, + + /// Holds user-created documents. See https://developer.android.com/reference/android/os/Environment.html#DIRECTORY_DOCUMENTS. documents, } diff --git a/packages/path_provider/pubspec.yaml b/packages/path_provider/pubspec.yaml index 7350124ef32e..d9d39409ae7f 100644 --- a/packages/path_provider/pubspec.yaml +++ b/packages/path_provider/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for getting commonly used locations on the Android & iOS file systems, such as the temp and app data directories. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider -version: 1.4.2 +version: 1.4.3 flutter: plugin: From e3e38dbd08a493d0d87c5e16a73e276ffb5e54ea Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 19 Nov 2019 14:22:08 -0800 Subject: [PATCH 21/47] [url_launcher] Update deps and docs in url_launcher_web (#2289) v 0.1.0 --- .../url_launcher_web/CHANGELOG.md | 4 ++++ .../url_launcher/url_launcher_web/README.md | 23 +++++++++++-------- .../url_launcher_web/pubspec.yaml | 7 +++--- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index e20f5fbbb636..a29a2ee1208e 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.0 + +- Update docs and pubspec. + # 0.0.2 - Switch to using `url_launcher_platform_interface`. diff --git a/packages/url_launcher/url_launcher_web/README.md b/packages/url_launcher/url_launcher_web/README.md index 66a586c57872..ddac7d870f98 100644 --- a/packages/url_launcher/url_launcher_web/README.md +++ b/packages/url_launcher/url_launcher_web/README.md @@ -4,22 +4,27 @@ The web implementation of [`url_launcher`][1]. ## Usage +### Import the package To use this plugin in your Flutter Web app, simply add it as a dependency in -your pubspec using a `git` dependency. This is only temporary: in the future -we hope to make this package an "endorsed" implementation of `url_launcher`, -so that it is automatically included in your Flutter Web app when you depend -on `package:url_launcher`. +your pubspec alongside the base `url_launcher` plugin. + +_(This is only temporary: in the future we hope to make this package an +"endorsed" implementation of `url_launcher`, so that it is automatically +included in your Flutter Web app when you depend on `package:url_launcher`.)_ + +This is what the above means to your `pubspec.yaml`: ```yaml +... dependencies: + ... url_launcher: ^5.1.4 - url_launcher_web: - git: - url: git://github.com/flutter/plugins.git - path: packages/url_launcher/url_launcher_web + url_launcher_web: ^0.1.0 + ... ``` +### Use the plugin Once you have the `url_launcher_web` dependency in your pubspec, you should be able to use `package:url_launcher` as normal. -[1]: ../url_launcher +[1]: ../url_launcher/url_launcher diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index e94a4d0d81c0..16c84fae366a 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_web description: Web platform implementation of url_launcher author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_web -version: 0.0.2 +version: 0.1.0 flutter: plugin: @@ -12,18 +12,17 @@ flutter: fileName: url_launcher_web.dart dependencies: + url_launcher_platform_interface: ^1.0.1 flutter: sdk: flutter flutter_web_plugins: sdk: flutter meta: ^1.1.7 - url_launcher_platform_interface: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter - url_launcher: - path: ../url_launcher + url_launcher: ^5.2.5 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" From fb471439774b24135f02b758091bbe1da3df566a Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 20 Nov 2019 08:36:55 -0800 Subject: [PATCH 22/47] Migrate plugins to use e2e tests. (#2282) --- packages/google_maps_flutter/CHANGELOG.md | 4 + .../google_maps_flutter/android/build.gradle | 26 ++++ .../google_maps_flutter/example/pubspec.yaml | 1 + ...{google_maps.dart => google_maps_e2e.dart} | 129 +++++++++--------- .../test_driver/google_maps_e2e_test.dart} | 6 +- .../example/test_driver/test_widgets.dart | 12 -- packages/google_maps_flutter/pubspec.yaml | 2 +- packages/path_provider/CHANGELOG.md | 5 + packages/path_provider/android/build.gradle | 26 ++++ .../example/android/gradle.properties | 2 + packages/path_provider/example/pubspec.yaml | 1 + ...h_provider.dart => path_provider_e2e.dart} | 18 ++- .../test_driver/path_provider_e2e_test.dart} | 6 +- packages/path_provider/pubspec.yaml | 2 +- 14 files changed, 148 insertions(+), 92 deletions(-) rename packages/google_maps_flutter/example/test_driver/{google_maps.dart => google_maps_e2e.dart} (86%) rename packages/{path_provider/example/test_driver/path_provider_test.dart => google_maps_flutter/example/test_driver/google_maps_e2e_test.dart} (72%) delete mode 100644 packages/google_maps_flutter/example/test_driver/test_widgets.dart rename packages/path_provider/example/test_driver/{path_provider.dart => path_provider_e2e.dart} (85%) rename packages/{google_maps_flutter/example/test_driver/google_maps_test.dart => path_provider/example/test_driver/path_provider_e2e_test.dart} (72%) diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index 6ea04694815a..0bf3943256ed 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.21+12 + +* Update driver tests in the example app to e2e tests. + ## 0.5.21+11 * Define clang module for iOS, fix analyzer warnings. diff --git a/packages/google_maps_flutter/android/build.gradle b/packages/google_maps_flutter/android/build.gradle index f13de0b79a18..9baaea837d11 100644 --- a/packages/google_maps_flutter/android/build.gradle +++ b/packages/google_maps_flutter/android/build.gradle @@ -36,3 +36,29 @@ android { implementation 'com.google.android.gms:play-services-maps:17.0.0' } } + +// TODO(cyanglaz): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} \ No newline at end of file diff --git a/packages/google_maps_flutter/example/pubspec.yaml b/packages/google_maps_flutter/example/pubspec.yaml index 799d80e76328..7e533a8de3b3 100644 --- a/packages/google_maps_flutter/example/pubspec.yaml +++ b/packages/google_maps_flutter/example/pubspec.yaml @@ -15,6 +15,7 @@ dev_dependencies: flutter_driver: sdk: flutter test: ^1.6.0 + e2e: ^0.2.1 # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/packages/google_maps_flutter/example/test_driver/google_maps.dart b/packages/google_maps_flutter/example/test_driver/google_maps_e2e.dart similarity index 86% rename from packages/google_maps_flutter/example/test_driver/google_maps.dart rename to packages/google_maps_flutter/example/test_driver/google_maps_e2e.dart index 90d065fbeb5b..53d0c90f99de 100644 --- a/packages/google_maps_flutter/example/test_driver/google_maps.dart +++ b/packages/google_maps_flutter/example/test_driver/google_maps_e2e.dart @@ -7,29 +7,25 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:e2e/e2e.dart'; import 'google_map_inspector.dart'; -import 'test_widgets.dart'; const LatLng _kInitialMapCenter = LatLng(0, 0); +const double _kInitialZoomLevel = 5; const CameraPosition _kInitialCameraPosition = - CameraPosition(target: _kInitialMapCenter); + CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel); void main() { - final Completer allTestsCompleter = Completer(); - enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future); + E2EWidgetsFlutterBinding.ensureInitialized(); - tearDownAll(() => allTestsCompleter.complete(null)); - - test('testCompassToggle', () async { + testWidgets('testCompassToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -48,7 +44,7 @@ void main() { bool compassEnabled = await inspector.isCompassEnabled(); expect(compassEnabled, false); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -64,12 +60,12 @@ void main() { expect(compassEnabled, true); }); - test('testMapToolbarToggle', () async { + testWidgets('testMapToolbarToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -88,7 +84,7 @@ void main() { bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(); expect(mapToolbarEnabled, false); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -104,7 +100,7 @@ void main() { expect(mapToolbarEnabled, Platform.isAndroid); }); - test('updateMinMaxZoomLevels', () async { + testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); @@ -112,7 +108,7 @@ void main() { const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(2, 4); const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(3, 8); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -131,7 +127,7 @@ void main() { MinMaxZoomPreference zoomLevel = await inspector.getMinMaxZoomLevels(); expect(zoomLevel, equals(initialZoomLevel)); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -147,12 +143,12 @@ void main() { expect(zoomLevel, equals(finalZoomLevel)); }); - test('testZoomGesturesEnabled', () async { + testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -171,7 +167,7 @@ void main() { bool zoomGesturesEnabled = await inspector.isZoomGesturesEnabled(); expect(zoomGesturesEnabled, false); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -187,12 +183,12 @@ void main() { expect(zoomGesturesEnabled, true); }); - test('testRotateGesturesEnabled', () async { + testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -211,7 +207,7 @@ void main() { bool rotateGesturesEnabled = await inspector.isRotateGesturesEnabled(); expect(rotateGesturesEnabled, false); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -227,12 +223,12 @@ void main() { expect(rotateGesturesEnabled, true); }); - test('testTiltGesturesEnabled', () async { + testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -251,7 +247,7 @@ void main() { bool tiltGesturesEnabled = await inspector.isTiltGesturesEnabled(); expect(tiltGesturesEnabled, false); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -267,12 +263,12 @@ void main() { expect(tiltGesturesEnabled, true); }); - test('testScrollGesturesEnabled', () async { + testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -291,7 +287,7 @@ void main() { bool scrollGesturesEnabled = await inspector.isScrollGesturesEnabled(); expect(scrollGesturesEnabled, false); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -307,7 +303,7 @@ void main() { expect(scrollGesturesEnabled, true); }); - test('testGetVisibleRegion', () async { + testWidgets('testGetVisibleRegion', (WidgetTester tester) async { final Key key = GlobalKey(); final LatLngBounds zeroLatLngBounds = LatLngBounds( southwest: const LatLng(0, 0), northeast: const LatLng(0, 0)); @@ -315,7 +311,7 @@ void main() { final Completer mapControllerCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -325,16 +321,15 @@ void main() { }, ), )); - final GoogleMapController mapController = - await mapControllerCompleter.future; - // We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at // initialization. https://github.com/flutter/flutter/issues/24806 // This temporary workaround fix is provided while the actual fix in the Google Maps SDK is // still being investigated. // TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved. // https://github.com/flutter/flutter/issues/27550 - await Future.delayed(const Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); + final GoogleMapController mapController = + await mapControllerCompleter.future; final LatLngBounds firstVisibleRegion = await mapController.getVisibleRegion(); @@ -345,8 +340,12 @@ void main() { expect(firstVisibleRegion, isNot(zeroLatLngBounds)); expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue); - const LatLng southWest = LatLng(60, 75); - const LatLng northEast = LatLng(65, 80); + // Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`. + // The size of the `LatLngBounds` is 10 by 10. + final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20, + firstVisibleRegion.southwest.longitude - 20); + final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10, + firstVisibleRegion.southwest.longitude - 10); final LatLng newCenter = LatLng( (northEast.latitude + southWest.latitude) / 2, (northEast.longitude + southWest.longitude) / 2, @@ -363,6 +362,7 @@ void main() { final double padding = 0; await mapController .moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding)); + await tester.pumpAndSettle(const Duration(seconds: 3)); final LatLngBounds secondVisibleRegion = await mapController.getVisibleRegion(); @@ -376,12 +376,12 @@ void main() { expect(secondVisibleRegion.contains(newCenter), isTrue); }); - test('testTraffic', () async { + testWidgets('testTraffic', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -401,12 +401,12 @@ void main() { expect(isTrafficEnabled, true); }); - test('testMyLocationButtonToggle', () async { + testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -426,7 +426,7 @@ void main() { bool myLocationButtonEnabled = await inspector.isMyLocationButtonEnabled(); expect(myLocationButtonEnabled, true); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -443,12 +443,13 @@ void main() { expect(myLocationButtonEnabled, false); }); - test('testMyLocationButton initial value false', () async { + testWidgets('testMyLocationButton initial value false', + (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -470,12 +471,13 @@ void main() { expect(myLocationButtonEnabled, false); }); - test('testMyLocationButton initial value true', () async { + testWidgets('testMyLocationButton initial value true', + (WidgetTester tester) async { final Key key = GlobalKey(); final Completer inspectorCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -497,12 +499,12 @@ void main() { expect(myLocationButtonEnabled, true); }); - test('testSetMapStyle valid Json String', () async { + testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -519,12 +521,13 @@ void main() { await controller.setMapStyle(mapStyle); }); - test('testSetMapStyle invalid Json String', () async { + testWidgets('testSetMapStyle invalid Json String', + (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -546,12 +549,12 @@ void main() { } }); - test('testSetMapStyle null string', () async { + testWidgets('testSetMapStyle null string', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -566,12 +569,12 @@ void main() { await controller.setMapStyle(null); }); - test('testGetLatLng', () async { + testWidgets('testGetLatLng', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -590,7 +593,7 @@ void main() { // still being investigated. // TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved. // https://github.com/flutter/flutter/issues/27550 - await Future.delayed(const Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng topLeft = @@ -603,12 +606,12 @@ void main() { expect(topLeft, northWest); }); - test('testScreenCoordinate', () async { + testWidgets('testScreenCoordinate', (WidgetTester tester) async { final Key key = GlobalKey(); final Completer controllerCompleter = Completer(); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: GoogleMap( key: key, @@ -618,7 +621,6 @@ void main() { }, ), )); - final GoogleMapController controller = await controllerCompleter.future; // We suspected a bug in the iOS Google Maps SDK caused the camera is not properly positioned at @@ -627,7 +629,7 @@ void main() { // still being investigated. // TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved. // https://github.com/flutter/flutter/issues/27550 - await Future.delayed(const Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); final LatLngBounds visibleRegion = await controller.getVisibleRegion(); final LatLng northWest = LatLng( @@ -636,11 +638,10 @@ void main() { ); final ScreenCoordinate topLeft = await controller.getScreenCoordinate(northWest); - expect(topLeft, const ScreenCoordinate(x: 0, y: 0)); }); - test('testResizeWidget', () async { + testWidgets('testResizeWidget', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final GoogleMap map = GoogleMap( @@ -649,14 +650,14 @@ void main() { controllerCompleter.complete(controller); }, ); - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( body: SizedBox(height: 100, width: 100, child: map))))); final GoogleMapController controller = await controllerCompleter.future; - await pumpWidget(Directionality( + await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: MaterialApp( home: Scaffold( @@ -668,7 +669,7 @@ void main() { // still being investigated. // TODO(cyanglaz): Remove this temporary fix once the Maps SDK issue is resolved. // https://github.com/flutter/flutter/issues/27550 - await Future.delayed(const Duration(seconds: 3)); + await tester.pumpAndSettle(const Duration(seconds: 3)); // Simple call to make sure that the app hasn't crashed. final LatLngBounds bounds1 = await controller.getVisibleRegion(); diff --git a/packages/path_provider/example/test_driver/path_provider_test.dart b/packages/google_maps_flutter/example/test_driver/google_maps_e2e_test.dart similarity index 72% rename from packages/path_provider/example/test_driver/path_provider_test.dart rename to packages/google_maps_flutter/example/test_driver/google_maps_e2e_test.dart index b0d3305cd652..ff6e9ce74ad9 100644 --- a/packages/path_provider/example/test_driver/path_provider_test.dart +++ b/packages/google_maps_flutter/example/test_driver/google_maps_e2e_test.dart @@ -3,11 +3,13 @@ // 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(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); driver.close(); + exit(result == 'pass' ? 0 : 1); } diff --git a/packages/google_maps_flutter/example/test_driver/test_widgets.dart b/packages/google_maps_flutter/example/test_driver/test_widgets.dart deleted file mode 100644 index 5656c9f5610c..000000000000 --- a/packages/google_maps_flutter/example/test_driver/test_widgets.dart +++ /dev/null @@ -1,12 +0,0 @@ -// 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 'package:flutter/widgets.dart'; - -Future pumpWidget(Widget widget) { - runApp(widget); - return WidgetsBinding.instance.endOfFrame; -} diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index fc683bb1bba6..320e942bf208 100644 --- a/packages/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter -version: 0.5.21+11 +version: 0.5.21+12 dependencies: flutter: diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md index a8c91a2071f5..27dde4284b77 100644 --- a/packages/path_provider/CHANGELOG.md +++ b/packages/path_provider/CHANGELOG.md @@ -1,5 +1,10 @@ +## 1.4.4 + +* Update driver tests in the example app to e2e tests. + ## 1.4.3 +* Update driver tests in the example app to e2e tests. * Add missing DartDocs and a lint to prevent further regressions. ## 1.4.2 diff --git a/packages/path_provider/android/build.gradle b/packages/path_provider/android/build.gradle index 93460e761568..71ea49b2fb39 100644 --- a/packages/path_provider/android/build.gradle +++ b/packages/path_provider/android/build.gradle @@ -37,3 +37,29 @@ dependencies { implementation 'androidx.annotation:annotation:1.1.0' testImplementation 'junit:junit:4.12' } + +// TODO(cyanglaz): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} \ No newline at end of file diff --git a/packages/path_provider/example/android/gradle.properties b/packages/path_provider/example/android/gradle.properties index 7be3d8b46841..38c8d4544ff1 100644 --- a/packages/path_provider/example/android/gradle.properties +++ b/packages/path_provider/example/android/gradle.properties @@ -1,2 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/path_provider/example/pubspec.yaml b/packages/path_provider/example/pubspec.yaml index 015a4fb46e5d..279a9138a608 100644 --- a/packages/path_provider/example/pubspec.yaml +++ b/packages/path_provider/example/pubspec.yaml @@ -11,6 +11,7 @@ dev_dependencies: flutter_driver: sdk: flutter test: any + e2e: ^0.2.1 flutter: uses-material-design: true diff --git a/packages/path_provider/example/test_driver/path_provider.dart b/packages/path_provider/example/test_driver/path_provider_e2e.dart similarity index 85% rename from packages/path_provider/example/test_driver/path_provider.dart rename to packages/path_provider/example/test_driver/path_provider_e2e.dart index ccbc62599e02..d3a1019a7c23 100644 --- a/packages/path_provider/example/test_driver/path_provider.dart +++ b/packages/path_provider/example/test_driver/path_provider_e2e.dart @@ -5,31 +5,29 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:e2e/e2e.dart'; void main() { - final Completer allTestsCompleter = Completer(); - enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future); - tearDownAll(() => allTestsCompleter.complete(null)); + E2EWidgetsFlutterBinding.ensureInitialized(); - test('getTemporaryDirectory', () async { + testWidgets('getTemporaryDirectory', (WidgetTester tester) async { final Directory result = await getTemporaryDirectory(); _verifySampleFile(result, 'temporaryDirectory'); }); - test('getApplicationDocumentsDirectory', () async { + testWidgets('getApplicationDocumentsDirectory', (WidgetTester tester) async { final Directory result = await getApplicationDocumentsDirectory(); _verifySampleFile(result, 'applicationDocuments'); }); - test('getApplicationSupportDirectory', () async { + testWidgets('getApplicationSupportDirectory', (WidgetTester tester) async { final Directory result = await getApplicationSupportDirectory(); _verifySampleFile(result, 'applicationSupport'); }); - test('getLibraryDirectory', () async { + testWidgets('getLibraryDirectory', (WidgetTester tester) async { if (Platform.isIOS) { final Directory result = await getLibraryDirectory(); _verifySampleFile(result, 'library'); @@ -39,7 +37,7 @@ void main() { } }); - test('getExternalStorageDirectory', () async { + testWidgets('getExternalStorageDirectory', (WidgetTester tester) async { if (Platform.isIOS) { final Future result = getExternalStorageDirectory(); expect(result, throwsA(isInstanceOf())); @@ -49,7 +47,7 @@ void main() { } }); - test('getExternalCacheDirectories', () async { + testWidgets('getExternalCacheDirectories', (WidgetTester tester) async { if (Platform.isIOS) { final Future> result = getExternalCacheDirectories(); expect(result, throwsA(isInstanceOf())); diff --git a/packages/google_maps_flutter/example/test_driver/google_maps_test.dart b/packages/path_provider/example/test_driver/path_provider_e2e_test.dart similarity index 72% rename from packages/google_maps_flutter/example/test_driver/google_maps_test.dart rename to packages/path_provider/example/test_driver/path_provider_e2e_test.dart index b0d3305cd652..ff6e9ce74ad9 100644 --- a/packages/google_maps_flutter/example/test_driver/google_maps_test.dart +++ b/packages/path_provider/example/test_driver/path_provider_e2e_test.dart @@ -3,11 +3,13 @@ // 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(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); driver.close(); + exit(result == 'pass' ? 0 : 1); } diff --git a/packages/path_provider/pubspec.yaml b/packages/path_provider/pubspec.yaml index d9d39409ae7f..15aa1a07db8b 100644 --- a/packages/path_provider/pubspec.yaml +++ b/packages/path_provider/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for getting commonly used locations on the Android & iOS file systems, such as the temp and app data directories. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider -version: 1.4.3 +version: 1.4.4 flutter: plugin: From d28c98c5e9ab3c0c62ffbe59d8d265f8b6c23070 Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Wed, 20 Nov 2019 13:57:48 -0800 Subject: [PATCH 23/47] [video_player] Move [player dispose] to `onUnregistered` (#2124) --- packages/video_player/CHANGELOG.md | 6 + packages/video_player/example/lib/main.dart | 209 ++++++++++++------ packages/video_player/example/pubspec.yaml | 5 +- .../example/test_driver/video_player.dart | 11 + .../example/test_driver/video_player_e2e.dart | 1 - .../test_driver/video_player_e2e_test.dart | 1 - .../test_driver/video_player_test.dart | 27 +++ .../ios/Classes/VideoPlayerPlugin.m | 23 +- packages/video_player/pubspec.yaml | 2 +- 9 files changed, 216 insertions(+), 69 deletions(-) create mode 100644 packages/video_player/example/test_driver/video_player.dart create mode 100644 packages/video_player/example/test_driver/video_player_test.dart diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index 759c955e39df..fa84c90f5a61 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.10.3+1 + +* Dispose `FLTVideoPlayer` in `onTextureUnregistered` callback on iOS. +* Add a temporary fix to dispose the `FLTVideoPlayer` with a delay to avoid race condition. +* Updated the example app to include a new page that pop back after video is done playing. + ## 0.10.3 * Add support for the v2 Android embedding. This shouldn't impact existing diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/example/lib/main.dart index 196b90846e16..43d9355c4345 100644 --- a/packages/video_player/example/lib/main.dart +++ b/packages/video_player/example/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; +import 'package:flutter/scheduler.dart'; /// Controls play and pause of [controller]. /// @@ -28,7 +29,7 @@ class VideoPlayPause extends StatefulWidget { class _VideoPlayPauseState extends State { _VideoPlayPauseState() { listener = () { - setState(() {}); + SchedulerBinding.instance.addPostFrameCallback((_) => setState(() {})); }; } @@ -48,8 +49,11 @@ class _VideoPlayPauseState extends State { @override void deactivate() { - controller.setVolume(0.0); - controller.removeListener(listener); + SchedulerBinding.instance.addPostFrameCallback((_) { + controller.setVolume(0.0); + controller.removeListener(listener); + }); + super.deactivate(); } @@ -360,73 +364,152 @@ class AspectRatioVideoState extends State { } } -void main() { - runApp( - MaterialApp( - home: DefaultTabController( - length: 3, - child: Scaffold( - appBar: AppBar( - title: const Text('Video player example'), - bottom: const TabBar( - isScrollable: true, - tabs: [ - Tab( - icon: Icon(Icons.cloud), - text: "Remote", - ), - Tab(icon: Icon(Icons.insert_drive_file), text: "Asset"), - Tab(icon: Icon(Icons.list), text: "List example"), - ], - ), +class App extends StatelessWidget { + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 3, + child: Scaffold( + key: const ValueKey('home_page'), + appBar: AppBar( + title: const Text('Video player example'), + actions: [ + IconButton( + key: const ValueKey('push_tab'), + icon: const Icon(Icons.navigation), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + PlayerVideoAndPopPage()), + ); + }, + ) + ], + bottom: const TabBar( + isScrollable: true, + tabs: [ + Tab( + icon: Icon(Icons.cloud), + text: "Remote", + ), + Tab(icon: Icon(Icons.insert_drive_file), text: "Asset"), + Tab(icon: Icon(Icons.list), text: "List example"), + ], ), - body: TabBarView( - children: [ - SingleChildScrollView( - child: Column( - children: [ - Container( - padding: const EdgeInsets.only(top: 20.0), + ), + body: TabBarView( + children: [ + SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20.0), + ), + const Text('With remote mp4'), + Container( + padding: const EdgeInsets.all(20), + child: NetworkPlayerLifeCycle( + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + (BuildContext context, + VideoPlayerController controller) => + AspectRatioVideo(controller), ), - const Text('With remote mp4'), - Container( - padding: const EdgeInsets.all(20), - child: NetworkPlayerLifeCycle( - 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4', + ), + ], + ), + ), + SingleChildScrollView( + child: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20.0), + ), + const Text('With assets mp4'), + Container( + padding: const EdgeInsets.all(20), + child: AssetPlayerLifeCycle( + 'assets/Butterfly-209.mp4', (BuildContext context, VideoPlayerController controller) => - AspectRatioVideo(controller), - ), - ), - ], - ), + AspectRatioVideo(controller)), + ), + ], ), - SingleChildScrollView( - child: Column( - children: [ - Container( - padding: const EdgeInsets.only(top: 20.0), - ), - const Text('With assets mp4'), - Container( - padding: const EdgeInsets.all(20), - child: AssetPlayerLifeCycle( - 'assets/Butterfly-209.mp4', - (BuildContext context, - VideoPlayerController controller) => - AspectRatioVideo(controller)), - ), - ], - ), - ), - AssetPlayerLifeCycle( - 'assets/Butterfly-209.mp4', - (BuildContext context, VideoPlayerController controller) => - VideoInListOfCards(controller)), - ], - ), + ), + AssetPlayerLifeCycle( + 'assets/Butterfly-209.mp4', + (BuildContext context, VideoPlayerController controller) => + VideoInListOfCards(controller)), + ], ), ), + ); + } +} + +void main() { + runApp( + MaterialApp( + home: App(), ), ); } + +class PlayerVideoAndPopPage extends StatefulWidget { + @override + _PlayerVideoAndPopPageState createState() => _PlayerVideoAndPopPageState(); +} + +class _PlayerVideoAndPopPageState extends State { + VideoPlayerController _videoPlayerController; + bool startedPlaying = false; + + @override + void initState() { + super.initState(); + + _videoPlayerController = + VideoPlayerController.asset('assets/Butterfly-209.mp4'); + _videoPlayerController.addListener(() { + if (startedPlaying && !_videoPlayerController.value.isPlaying) { + Navigator.pop(context); + } + }); + } + + @override + void dispose() { + _videoPlayerController.dispose(); + super.dispose(); + } + + Future started() async { + await _videoPlayerController.initialize(); + await _videoPlayerController.play(); + startedPlaying = true; + return true; + } + + @override + Widget build(BuildContext context) { + return Material( + elevation: 0, + child: Center( + child: FutureBuilder( + future: started(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.data == true) { + return AspectRatio( + aspectRatio: _videoPlayerController.value.aspectRatio, + child: VideoPlayer(_videoPlayerController)); + } else { + return const Text('waiting for video to load'); + } + }, + ), + ), + ); + } +} diff --git a/packages/video_player/example/pubspec.yaml b/packages/video_player/example/pubspec.yaml index bcb559b35bb5..b83e8d177646 100644 --- a/packages/video_player/example/pubspec.yaml +++ b/packages/video_player/example/pubspec.yaml @@ -4,6 +4,8 @@ description: Demonstrates how to use the video_player plugin. dependencies: flutter: sdk: flutter + video_player: + path: ../ dev_dependencies: flutter_test: @@ -11,8 +13,7 @@ dev_dependencies: flutter_driver: sdk: flutter e2e: "^0.2.0" - video_player: - path: ../ + test: any flutter: uses-material-design: true diff --git a/packages/video_player/example/test_driver/video_player.dart b/packages/video_player/example/test_driver/video_player.dart new file mode 100644 index 000000000000..cc498f41fccb --- /dev/null +++ b/packages/video_player/example/test_driver/video_player.dart @@ -0,0 +1,11 @@ +// 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_driver/driver_extension.dart'; +import 'package:video_player_example/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/packages/video_player/example/test_driver/video_player_e2e.dart b/packages/video_player/example/test_driver/video_player_e2e.dart index e726ab2f410d..bf35cf50b728 100644 --- a/packages/video_player/example/test_driver/video_player_e2e.dart +++ b/packages/video_player/example/test_driver/video_player_e2e.dart @@ -12,7 +12,6 @@ const Duration _playDuration = Duration(seconds: 1); void main() { E2EWidgetsFlutterBinding.ensureInitialized(); VideoPlayerController _controller; - tearDown(() async => _controller.dispose()); group('asset videos', () { diff --git a/packages/video_player/example/test_driver/video_player_e2e_test.dart b/packages/video_player/example/test_driver/video_player_e2e_test.dart index ccd716607d60..f3aa9e218d82 100644 --- a/packages/video_player/example/test_driver/video_player_e2e_test.dart +++ b/packages/video_player/example/test_driver/video_player_e2e_test.dart @@ -4,7 +4,6 @@ import 'dart:async'; import 'dart:io'; - import 'package:flutter_driver/flutter_driver.dart'; Future main() async { diff --git a/packages/video_player/example/test_driver/video_player_test.dart b/packages/video_player/example/test_driver/video_player_test.dart new file mode 100644 index 000000000000..a72c6944cfbe --- /dev/null +++ b/packages/video_player/example/test_driver/video_player_test.dart @@ -0,0 +1,27 @@ +// 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 'package:flutter_driver/flutter_driver.dart'; +import 'package:test/test.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + tearDownAll(() async { + driver.close(); + }); + + //TODO(cyanglaz): Use TabBar tabs to navigate between pages after https://github.com/flutter/flutter/issues/16991 is fixed. + //TODO(cyanglaz): Un-skip the test after https://github.com/flutter/flutter/issues/43012 is fixed + test('Push a page contains video and pop back, do not crash.', () async { + final SerializableFinder pushTab = find.byValueKey('push_tab'); + await driver.waitFor(pushTab); + await driver.tap(pushTab); + await driver.waitForAbsent(pushTab); + await driver.waitFor(find.byValueKey('home_page')); + await driver.waitUntilNoTransientCallbacks(); + final Health health = await driver.checkHealth(); + expect(health.status, HealthStatus.ok); + }, skip: 'Cirrus CI currently hangs while playing videos'); +} diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/ios/Classes/VideoPlayerPlugin.m index bf449ec0e8e2..9bf61d8fed14 100644 --- a/packages/video_player/ios/Classes/VideoPlayerPlugin.m +++ b/packages/video_player/ios/Classes/VideoPlayerPlugin.m @@ -363,6 +363,12 @@ - (CVPixelBufferRef)copyPixelBuffer { } } +- (void)onTextureUnregistered { + dispatch_async(dispatch_get_main_queue(), ^{ + [self dispose]; + }); +} + - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments { _eventSink = nil; return nil; @@ -487,7 +493,22 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@"dispose" isEqualToString:call.method]) { [_registry unregisterTexture:textureId]; [_players removeObjectForKey:@(textureId)]; - [player dispose]; + // If the Flutter contains https://github.com/flutter/engine/pull/12695, + // the `player` is disposed via `onTextureUnregistered` at the right time. + // Without https://github.com/flutter/engine/pull/12695, there is no guarantee that the + // texture has completed the un-reregistration. It may leads a crash if we dispose the + // `player` before the texture is unregistered. We add a dispatch_after hack to make sure the + // texture is unregistered before we dispose the `player`. + // + // TODO(cyanglaz): Remove this dispatch block when + // https://github.com/flutter/flutter/commit/8159a9906095efc9af8b223f5e232cb63542ad0b is in + // stable And update the min flutter version of the plugin to the stable version. + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + if (!player.disposed) { + [player dispose]; + } + }); result(nil); } else if ([@"setLooping" isEqualToString:call.method]) { [player setIsLooping:[argsMap[@"looping"] boolValue]]; diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index 656a81fa8643..516b6f778c84 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.3 +version: 0.10.3+1 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: From cba393233e559c925a4daf71b06b4bb01c606762 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Thu, 21 Nov 2019 23:54:06 +0100 Subject: [PATCH 24/47] [video_player] Add platform interface (#2273) * Move plugin to subdir * Add video_player_platform_interface * Make analyzer happy * Improve documentation * Bump version and update changelog * Add stream of VideoEvent * Ignore deprecated_member_use * Improve changelog message and version * Use DataSource class * Add duration, size and buffering to VideoEvent * Seek to Duration * Fix buffering update * Adapt docstring * Add buildView --- .../{ => video_player}/CHANGELOG.md | 4 + .../video_player/{ => video_player}/LICENSE | 0 .../video_player/{ => video_player}/README.md | 2 +- .../{ => video_player}/android/build.gradle | 0 .../android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../plugins/videoplayer/QueuingEventSink.java | 0 .../plugins/videoplayer/VideoPlayer.java | 0 .../videoplayer/VideoPlayerPlugin.java | 0 .../{ => video_player}/doc/demo_ipod.gif | Bin .../{ => video_player}/example/README.md | 0 .../{ => video_player}/example/android.iml | 0 .../example/android/app/build.gradle | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../EmbeddingV1Activity.java | 0 .../videoplayerexample/MainActivity.java | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values/styles.xml | 0 .../main/res/xml/network_security_config.xml | 0 .../example/android/build.gradle | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../example/android/settings.gradle | 0 .../example/assets/Butterfly-209.mp4 | Bin .../example/assets/flutter-mark-square-64.png | Bin .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../example/ios/Runner/AppDelegate.h | 0 .../example/ios/Runner/AppDelegate.m | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 0 .../example/ios/Runner/main.m | 0 .../{ => video_player}/example/lib/main.dart | 0 .../{ => video_player}/example/pubspec.yaml | 0 .../example/test_driver/video_player_e2e.dart | 0 .../test_driver/video_player_e2e_test.dart | 0 .../example/video_player_example.iml | 0 .../example/video_player_example_android.iml | 0 .../{ => video_player}/ios/Assets/.gitkeep | 0 .../ios/Classes/VideoPlayerPlugin.h | 0 .../ios/Classes/VideoPlayerPlugin.m | 0 .../ios/video_player.podspec | 0 .../{ => video_player}/lib/video_player.dart | 0 .../{ => video_player}/pubspec.yaml | 4 +- .../test/video_player_test.dart | 0 .../video_player_android.iml | 0 .../CHANGELOG.md | 3 + .../video_player_platform_interface/LICENSE | 27 ++ .../video_player_platform_interface/README.md | 26 ++ .../lib/method_channel_video_player.dart | 178 ++++++++++++ .../lib/video_player_platform_interface.dart | 189 ++++++++++++ .../pubspec.yaml | 21 ++ .../method_channel_video_player_test.dart | 273 ++++++++++++++++++ script/build_all_plugins_app.sh | 1 + 88 files changed, 725 insertions(+), 3 deletions(-) rename packages/video_player/{ => video_player}/CHANGELOG.md (98%) rename packages/video_player/{ => video_player}/LICENSE (100%) rename packages/video_player/{ => video_player}/README.md (97%) rename packages/video_player/{ => video_player}/android/build.gradle (100%) rename packages/video_player/{ => video_player}/android/gradle.properties (100%) rename packages/video_player/{ => video_player}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/video_player/{ => video_player}/android/settings.gradle (100%) rename packages/video_player/{ => video_player}/android/src/main/AndroidManifest.xml (100%) rename packages/video_player/{ => video_player}/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java (100%) rename packages/video_player/{ => video_player}/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java (100%) rename packages/video_player/{ => video_player}/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java (100%) rename packages/video_player/{ => video_player}/doc/demo_ipod.gif (100%) rename packages/video_player/{ => video_player}/example/README.md (100%) rename packages/video_player/{ => video_player}/example/android.iml (100%) rename packages/video_player/{ => video_player}/example/android/app/build.gradle (100%) rename packages/video_player/{ => video_player}/example/android/app/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/AndroidManifest.xml (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/values/styles.xml (100%) rename packages/video_player/{ => video_player}/example/android/app/src/main/res/xml/network_security_config.xml (100%) rename packages/video_player/{ => video_player}/example/android/build.gradle (100%) rename packages/video_player/{ => video_player}/example/android/gradle.properties (100%) rename packages/video_player/{ => video_player}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/video_player/{ => video_player}/example/android/settings.gradle (100%) rename packages/video_player/{ => video_player}/example/assets/Butterfly-209.mp4 (100%) rename packages/video_player/{ => video_player}/example/assets/flutter-mark-square-64.png (100%) rename packages/video_player/{ => video_player}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename packages/video_player/{ => video_player}/example/ios/Flutter/Debug.xcconfig (100%) rename packages/video_player/{ => video_player}/example/ios/Flutter/Release.xcconfig (100%) rename packages/video_player/{ => video_player}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename packages/video_player/{ => video_player}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename packages/video_player/{ => video_player}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/video_player/{ => video_player}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/AppDelegate.h (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/AppDelegate.m (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/Info.plist (100%) rename packages/video_player/{ => video_player}/example/ios/Runner/main.m (100%) rename packages/video_player/{ => video_player}/example/lib/main.dart (100%) rename packages/video_player/{ => video_player}/example/pubspec.yaml (100%) rename packages/video_player/{ => video_player}/example/test_driver/video_player_e2e.dart (100%) rename packages/video_player/{ => video_player}/example/test_driver/video_player_e2e_test.dart (100%) rename packages/video_player/{ => video_player}/example/video_player_example.iml (100%) rename packages/video_player/{ => video_player}/example/video_player_example_android.iml (100%) rename packages/video_player/{ => video_player}/ios/Assets/.gitkeep (100%) rename packages/video_player/{ => video_player}/ios/Classes/VideoPlayerPlugin.h (100%) rename packages/video_player/{ => video_player}/ios/Classes/VideoPlayerPlugin.m (100%) rename packages/video_player/{ => video_player}/ios/video_player.podspec (100%) rename packages/video_player/{ => video_player}/lib/video_player.dart (100%) rename packages/video_player/{ => video_player}/pubspec.yaml (92%) rename packages/video_player/{ => video_player}/test/video_player_test.dart (100%) rename packages/video_player/{ => video_player}/video_player_android.iml (100%) create mode 100644 packages/video_player/video_player_platform_interface/CHANGELOG.md create mode 100644 packages/video_player/video_player_platform_interface/LICENSE create mode 100644 packages/video_player/video_player_platform_interface/README.md create mode 100644 packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart create mode 100644 packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart create mode 100644 packages/video_player/video_player_platform_interface/pubspec.yaml create mode 100644 packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md similarity index 98% rename from packages/video_player/CHANGELOG.md rename to packages/video_player/video_player/CHANGELOG.md index fa84c90f5a61..1bac34f6649b 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.3+2 + +* Update the homepage to point to the new plugin location + ## 0.10.3+1 * Dispose `FLTVideoPlayer` in `onTextureUnregistered` callback on iOS. diff --git a/packages/video_player/LICENSE b/packages/video_player/video_player/LICENSE similarity index 100% rename from packages/video_player/LICENSE rename to packages/video_player/video_player/LICENSE diff --git a/packages/video_player/README.md b/packages/video_player/video_player/README.md similarity index 97% rename from packages/video_player/README.md rename to packages/video_player/video_player/README.md index 6b7420600e51..eae8f6cb3a46 100644 --- a/packages/video_player/README.md +++ b/packages/video_player/video_player/README.md @@ -4,7 +4,7 @@ A Flutter plugin for iOS and Android for playing back video on a Widget surface. -![The example app running in iOS](https://github.com/flutter/plugins/blob/master/packages/video_player/doc/demo_ipod.gif?raw=true) +![The example app running in iOS](https://github.com/flutter/plugins/blob/master/packages/video_player/video_player/doc/demo_ipod.gif?raw=true) *Note*: This plugin is still under development, and some APIs might not be available yet. [Feedback welcome](https://github.com/flutter/flutter/issues) and diff --git a/packages/video_player/android/build.gradle b/packages/video_player/video_player/android/build.gradle similarity index 100% rename from packages/video_player/android/build.gradle rename to packages/video_player/video_player/android/build.gradle diff --git a/packages/video_player/android/gradle.properties b/packages/video_player/video_player/android/gradle.properties similarity index 100% rename from packages/video_player/android/gradle.properties rename to packages/video_player/video_player/android/gradle.properties diff --git a/packages/video_player/android/gradle/wrapper/gradle-wrapper.properties b/packages/video_player/video_player/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/video_player/android/gradle/wrapper/gradle-wrapper.properties rename to packages/video_player/video_player/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/video_player/android/settings.gradle b/packages/video_player/video_player/android/settings.gradle similarity index 100% rename from packages/video_player/android/settings.gradle rename to packages/video_player/video_player/android/settings.gradle diff --git a/packages/video_player/android/src/main/AndroidManifest.xml b/packages/video_player/video_player/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/video_player/android/src/main/AndroidManifest.xml rename to packages/video_player/video_player/android/src/main/AndroidManifest.xml diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java similarity index 100% rename from packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java rename to packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/QueuingEventSink.java diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java similarity index 100% rename from packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java rename to packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java diff --git a/packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java similarity index 100% rename from packages/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java rename to packages/video_player/video_player/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java diff --git a/packages/video_player/doc/demo_ipod.gif b/packages/video_player/video_player/doc/demo_ipod.gif similarity index 100% rename from packages/video_player/doc/demo_ipod.gif rename to packages/video_player/video_player/doc/demo_ipod.gif diff --git a/packages/video_player/example/README.md b/packages/video_player/video_player/example/README.md similarity index 100% rename from packages/video_player/example/README.md rename to packages/video_player/video_player/example/README.md diff --git a/packages/video_player/example/android.iml b/packages/video_player/video_player/example/android.iml similarity index 100% rename from packages/video_player/example/android.iml rename to packages/video_player/video_player/example/android.iml diff --git a/packages/video_player/example/android/app/build.gradle b/packages/video_player/video_player/example/android/app/build.gradle similarity index 100% rename from packages/video_player/example/android/app/build.gradle rename to packages/video_player/video_player/example/android/app/build.gradle diff --git a/packages/video_player/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/video_player/video_player/example/android/app/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/video_player/example/android/app/gradle/wrapper/gradle-wrapper.properties rename to packages/video_player/video_player/example/android/app/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/video_player/example/android/app/src/main/AndroidManifest.xml b/packages/video_player/video_player/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/video_player/example/android/app/src/main/AndroidManifest.xml rename to packages/video_player/video_player/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java b/packages/video_player/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java similarity index 100% rename from packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java rename to packages/video_player/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/EmbeddingV1Activity.java diff --git a/packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java b/packages/video_player/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java similarity index 100% rename from packages/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java rename to packages/video_player/video_player/example/android/app/src/main/java/io/flutter/plugins/videoplayerexample/MainActivity.java diff --git a/packages/video_player/example/android/app/src/main/res/drawable/launch_background.xml b/packages/video_player/video_player/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/video_player/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/video_player/video_player/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/video_player/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/video_player/video_player/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/video_player/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/video_player/video_player/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/video_player/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/video_player/video_player/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/video_player/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/video_player/video_player/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/video_player/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/video_player/video_player/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/video_player/example/android/app/src/main/res/values/styles.xml b/packages/video_player/video_player/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/video_player/example/android/app/src/main/res/values/styles.xml rename to packages/video_player/video_player/example/android/app/src/main/res/values/styles.xml diff --git a/packages/video_player/example/android/app/src/main/res/xml/network_security_config.xml b/packages/video_player/video_player/example/android/app/src/main/res/xml/network_security_config.xml similarity index 100% rename from packages/video_player/example/android/app/src/main/res/xml/network_security_config.xml rename to packages/video_player/video_player/example/android/app/src/main/res/xml/network_security_config.xml diff --git a/packages/video_player/example/android/build.gradle b/packages/video_player/video_player/example/android/build.gradle similarity index 100% rename from packages/video_player/example/android/build.gradle rename to packages/video_player/video_player/example/android/build.gradle diff --git a/packages/video_player/example/android/gradle.properties b/packages/video_player/video_player/example/android/gradle.properties similarity index 100% rename from packages/video_player/example/android/gradle.properties rename to packages/video_player/video_player/example/android/gradle.properties diff --git a/packages/video_player/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/video_player/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/video_player/video_player/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/video_player/example/android/settings.gradle b/packages/video_player/video_player/example/android/settings.gradle similarity index 100% rename from packages/video_player/example/android/settings.gradle rename to packages/video_player/video_player/example/android/settings.gradle diff --git a/packages/video_player/example/assets/Butterfly-209.mp4 b/packages/video_player/video_player/example/assets/Butterfly-209.mp4 similarity index 100% rename from packages/video_player/example/assets/Butterfly-209.mp4 rename to packages/video_player/video_player/example/assets/Butterfly-209.mp4 diff --git a/packages/video_player/example/assets/flutter-mark-square-64.png b/packages/video_player/video_player/example/assets/flutter-mark-square-64.png similarity index 100% rename from packages/video_player/example/assets/flutter-mark-square-64.png rename to packages/video_player/video_player/example/assets/flutter-mark-square-64.png diff --git a/packages/video_player/example/ios/Flutter/AppFrameworkInfo.plist b/packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/video_player/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/video_player/video_player/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/video_player/example/ios/Flutter/Debug.xcconfig b/packages/video_player/video_player/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/video_player/example/ios/Flutter/Debug.xcconfig rename to packages/video_player/video_player/example/ios/Flutter/Debug.xcconfig diff --git a/packages/video_player/example/ios/Flutter/Release.xcconfig b/packages/video_player/video_player/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/video_player/example/ios/Flutter/Release.xcconfig rename to packages/video_player/video_player/example/ios/Flutter/Release.xcconfig diff --git a/packages/video_player/example/ios/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/video_player/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/video_player/video_player/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/video_player/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/video_player/video_player/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/video_player/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/video_player/video_player/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/video_player/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/video_player/video_player/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/video_player/example/ios/Runner/AppDelegate.h b/packages/video_player/video_player/example/ios/Runner/AppDelegate.h similarity index 100% rename from packages/video_player/example/ios/Runner/AppDelegate.h rename to packages/video_player/video_player/example/ios/Runner/AppDelegate.h diff --git a/packages/video_player/example/ios/Runner/AppDelegate.m b/packages/video_player/video_player/example/ios/Runner/AppDelegate.m similarity index 100% rename from packages/video_player/example/ios/Runner/AppDelegate.m rename to packages/video_player/video_player/example/ios/Runner/AppDelegate.m diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/video_player/video_player/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/video_player/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/video_player/video_player/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/video_player/example/ios/Runner/Base.lproj/Main.storyboard b/packages/video_player/video_player/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/video_player/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/video_player/video_player/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/video_player/example/ios/Runner/Info.plist b/packages/video_player/video_player/example/ios/Runner/Info.plist similarity index 100% rename from packages/video_player/example/ios/Runner/Info.plist rename to packages/video_player/video_player/example/ios/Runner/Info.plist diff --git a/packages/video_player/example/ios/Runner/main.m b/packages/video_player/video_player/example/ios/Runner/main.m similarity index 100% rename from packages/video_player/example/ios/Runner/main.m rename to packages/video_player/video_player/example/ios/Runner/main.m diff --git a/packages/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart similarity index 100% rename from packages/video_player/example/lib/main.dart rename to packages/video_player/video_player/example/lib/main.dart diff --git a/packages/video_player/example/pubspec.yaml b/packages/video_player/video_player/example/pubspec.yaml similarity index 100% rename from packages/video_player/example/pubspec.yaml rename to packages/video_player/video_player/example/pubspec.yaml diff --git a/packages/video_player/example/test_driver/video_player_e2e.dart b/packages/video_player/video_player/example/test_driver/video_player_e2e.dart similarity index 100% rename from packages/video_player/example/test_driver/video_player_e2e.dart rename to packages/video_player/video_player/example/test_driver/video_player_e2e.dart diff --git a/packages/video_player/example/test_driver/video_player_e2e_test.dart b/packages/video_player/video_player/example/test_driver/video_player_e2e_test.dart similarity index 100% rename from packages/video_player/example/test_driver/video_player_e2e_test.dart rename to packages/video_player/video_player/example/test_driver/video_player_e2e_test.dart diff --git a/packages/video_player/example/video_player_example.iml b/packages/video_player/video_player/example/video_player_example.iml similarity index 100% rename from packages/video_player/example/video_player_example.iml rename to packages/video_player/video_player/example/video_player_example.iml diff --git a/packages/video_player/example/video_player_example_android.iml b/packages/video_player/video_player/example/video_player_example_android.iml similarity index 100% rename from packages/video_player/example/video_player_example_android.iml rename to packages/video_player/video_player/example/video_player_example_android.iml diff --git a/packages/video_player/ios/Assets/.gitkeep b/packages/video_player/video_player/ios/Assets/.gitkeep similarity index 100% rename from packages/video_player/ios/Assets/.gitkeep rename to packages/video_player/video_player/ios/Assets/.gitkeep diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.h b/packages/video_player/video_player/ios/Classes/VideoPlayerPlugin.h similarity index 100% rename from packages/video_player/ios/Classes/VideoPlayerPlugin.h rename to packages/video_player/video_player/ios/Classes/VideoPlayerPlugin.h diff --git a/packages/video_player/ios/Classes/VideoPlayerPlugin.m b/packages/video_player/video_player/ios/Classes/VideoPlayerPlugin.m similarity index 100% rename from packages/video_player/ios/Classes/VideoPlayerPlugin.m rename to packages/video_player/video_player/ios/Classes/VideoPlayerPlugin.m diff --git a/packages/video_player/ios/video_player.podspec b/packages/video_player/video_player/ios/video_player.podspec similarity index 100% rename from packages/video_player/ios/video_player.podspec rename to packages/video_player/video_player/ios/video_player.podspec diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart similarity index 100% rename from packages/video_player/lib/video_player.dart rename to packages/video_player/video_player/lib/video_player.dart diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml similarity index 92% rename from packages/video_player/pubspec.yaml rename to packages/video_player/video_player/pubspec.yaml index 516b6f778c84..32d1e98e586c 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -2,8 +2,8 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.3+1 -homepage: https://github.com/flutter/plugins/tree/master/packages/video_player +version: 0.10.3+2 +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: plugin: diff --git a/packages/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart similarity index 100% rename from packages/video_player/test/video_player_test.dart rename to packages/video_player/video_player/test/video_player_test.dart diff --git a/packages/video_player/video_player_android.iml b/packages/video_player/video_player/video_player_android.iml similarity index 100% rename from packages/video_player/video_player_android.iml rename to packages/video_player/video_player/video_player_android.iml diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..0d8803f93540 --- /dev/null +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Initial release. diff --git a/packages/video_player/video_player_platform_interface/LICENSE b/packages/video_player/video_player_platform_interface/LICENSE new file mode 100644 index 000000000000..c89293372cf3 --- /dev/null +++ b/packages/video_player/video_player_platform_interface/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 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/video_player/video_player_platform_interface/README.md b/packages/video_player/video_player_platform_interface/README.md new file mode 100644 index 000000000000..02ba8e7166fe --- /dev/null +++ b/packages/video_player/video_player_platform_interface/README.md @@ -0,0 +1,26 @@ +# video_player_platform_interface + +A common platform interface for the [`video_player`][1] plugin. + +This interface allows platform-specific implementations of the `video_player` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `video_player`, extend +[`VideoPlayerPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`VideoPlayerPlatform` by calling +`VideoPlayerPlatform.instance = MyPlatformVideoPlayer()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../video_player +[2]: lib/video_player_platform_interface.dart diff --git a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart new file mode 100644 index 000000000000..593c72684c1d --- /dev/null +++ b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart @@ -0,0 +1,178 @@ +// 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 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'video_player_platform_interface.dart'; + +const MethodChannel _channel = MethodChannel('flutter.io/videoPlayer'); + +/// An implementation of [VideoPlayerPlatform] that uses method channels. +class MethodChannelVideoPlayer extends VideoPlayerPlatform { + @override + Future init() { + return _channel.invokeMethod('init'); + } + + @override + Future dispose(int textureId) { + return _channel.invokeMethod( + 'dispose', + {'textureId': textureId}, + ); + } + + @override + Future create(DataSource dataSource) async { + Map dataSourceDescription; + + switch (dataSource.sourceType) { + case DataSourceType.asset: + dataSourceDescription = { + 'asset': dataSource.asset, + 'package': dataSource.package, + }; + break; + case DataSourceType.network: + dataSourceDescription = { + 'uri': dataSource.uri, + 'formatHint': _videoFormatStringMap[dataSource.formatHint] + }; + break; + case DataSourceType.file: + dataSourceDescription = {'uri': dataSource.uri}; + break; + } + + final Map response = + await _channel.invokeMapMethod( + 'create', + dataSourceDescription, + ); + return response['textureId']; + } + + @override + Future setLooping(int textureId, bool looping) { + return _channel.invokeMethod( + 'setLooping', + { + 'textureId': textureId, + 'looping': looping, + }, + ); + } + + @override + Future play(int textureId) { + return _channel.invokeMethod( + 'play', + {'textureId': textureId}, + ); + } + + @override + Future pause(int textureId) { + return _channel.invokeMethod( + 'pause', + {'textureId': textureId}, + ); + } + + @override + Future setVolume(int textureId, double volume) { + return _channel.invokeMethod( + 'setVolume', + { + 'textureId': textureId, + 'volume': volume, + }, + ); + } + + @override + Future seekTo(int textureId, Duration position) { + return _channel.invokeMethod( + 'seekTo', + { + 'textureId': textureId, + 'location': position.inMilliseconds, + }, + ); + } + + @override + Future getPosition(int textureId) async { + return Duration( + milliseconds: await _channel.invokeMethod( + 'position', + {'textureId': textureId}, + ), + ); + } + + @override + Stream videoEventsFor(int textureId) { + return _eventChannelFor(textureId) + .receiveBroadcastStream() + .map((dynamic event) { + final Map map = event; + switch (map['event']) { + case 'initialized': + return VideoEvent( + eventType: VideoEventType.initialized, + duration: Duration(milliseconds: map['duration']), + size: Size(map['width']?.toDouble() ?? 0.0, + map['height']?.toDouble() ?? 0.0), + ); + case 'completed': + return VideoEvent( + eventType: VideoEventType.completed, + ); + case 'bufferingUpdate': + final List values = map['values']; + + return VideoEvent( + buffered: values.map(_toDurationRange).toList(), + eventType: VideoEventType.completed, + ); + case 'bufferingStart': + return VideoEvent(eventType: VideoEventType.bufferingStart); + case 'bufferingEnd': + return VideoEvent(eventType: VideoEventType.bufferingEnd); + default: + return VideoEvent(eventType: VideoEventType.unknown); + } + }); + } + + @override + Widget buildView(int textureId) { + return Texture(textureId: textureId); + } + + EventChannel _eventChannelFor(int textureId) { + return EventChannel('flutter.io/videoPlayer/videoEvents$textureId'); + } + + static const Map _videoFormatStringMap = + { + VideoFormat.ss: 'ss', + VideoFormat.hls: 'hls', + VideoFormat.dash: 'dash', + VideoFormat.other: 'other', + }; + + DurationRange _toDurationRange(dynamic value) { + final List pair = value; + return DurationRange( + Duration(milliseconds: pair[0]), + Duration(milliseconds: pair[1]), + ); + } +} diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart new file mode 100644 index 000000000000..6c4ecaf58509 --- /dev/null +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -0,0 +1,189 @@ +// 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 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/widgets.dart'; +import 'package:meta/meta.dart' show required, visibleForTesting; + +import 'method_channel_video_player.dart'; + +/// The interface that implementations of video_player must implement. +/// +/// Platform implementations should extend this class rather than implement it as `video_player` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [VideoPlayerPlatform] methods. +abstract class VideoPlayerPlatform { + /// Only mock implementations should set this to true. + /// + /// Mockito mocks are implementing this class with `implements` which is forbidden for anything + /// other than mocks (see class docs). This property provides a backdoor for mockito mocks to + /// skip the verification that the class isn't implemented with `implements`. + @visibleForTesting + bool get isMock => false; + + /// The default instance of [VideoPlayerPlatform] to use. + /// + /// Platform-specific plugins should override this with their own + /// platform-specific class that extends [VideoPlayerPlatform] when they + /// register themselves. + /// + /// Defaults to [MethodChannelVideoPlayer]. + static VideoPlayerPlatform _instance = MethodChannelVideoPlayer(); + + static VideoPlayerPlatform get instance => _instance; + + // TODO(amirh): Extract common platform interface logic. + // https://github.com/flutter/flutter/issues/43368 + static set instance(VideoPlayerPlatform instance) { + if (!instance.isMock) { + try { + instance._verifyProvidesDefaultImplementations(); + } on NoSuchMethodError catch (_) { + throw AssertionError( + 'Platform interfaces must not be implemented with `implements`'); + } + } + _instance = instance; + } + + /// Initializes the platform interface and disposes all existing players. + /// + /// This method is called when the plugin is first initialized + /// and on every full restart. + Future init() { + throw UnimplementedError('init() has not been implemented.'); + } + + /// Clears one video. + Future dispose(int textureId) { + throw UnimplementedError('dispose() has not been implemented.'); + } + + /// Creates an instance of a video player and returns its textureId. + Future create(DataSource dataSource) { + throw UnimplementedError('create() has not been implemented.'); + } + + /// Returns a Stream of [VideoEventType]s. + Stream videoEventsFor(int textureId) { + throw UnimplementedError('videoEventsFor() has not been implemented.'); + } + + /// Sets the looping attribute of the video. + Future setLooping(int textureId, bool looping) { + throw UnimplementedError('setLooping() has not been implemented.'); + } + + /// Starts the video playback. + Future play(int textureId) { + throw UnimplementedError('play() has not been implemented.'); + } + + /// Stops the video playback. + Future pause(int textureId) { + throw UnimplementedError('pause() has not been implemented.'); + } + + /// Sets the volume to a range between 0.0 and 1.0. + Future setVolume(int textureId, double volume) { + throw UnimplementedError('setVolume() has not been implemented.'); + } + + /// Sets the video position to a [Duration] from the start. + Future seekTo(int textureId, Duration position) { + throw UnimplementedError('seekTo() has not been implemented.'); + } + + /// Gets the video position as [Duration] from the start. + Future getPosition(int textureId) { + throw UnimplementedError('getPosition() has not been implemented.'); + } + + /// Returns a widget displaying the video with a given textureID. + Widget buildView(int textureId) { + throw UnimplementedError('buildView() has not been implemented.'); + } + + // This method makes sure that VideoPlayer isn't implemented with `implements`. + // + // See class doc for more details on why implementing this class is forbidden. + // + // This private method is called by the instance setter, which fails if the class is + // implemented with `implements`. + void _verifyProvidesDefaultImplementations() {} +} + +class DataSource { + DataSource({ + @required this.sourceType, + this.uri, + this.formatHint, + this.asset, + this.package, + }); + + final DataSourceType sourceType; + final String uri; + final VideoFormat formatHint; + final String asset; + final String package; +} + +enum DataSourceType { + asset, + network, + file, +} + +enum VideoFormat { + dash, + hls, + ss, + other, +} + +class VideoEvent { + VideoEvent({ + @required this.eventType, + this.duration, + this.size, + this.buffered, + }); + + final VideoEventType eventType; + final Duration duration; + final Size size; + final List buffered; +} + +enum VideoEventType { + initialized, + completed, + bufferingUpdate, + bufferingStart, + bufferingEnd, + unknown, +} + +class DurationRange { + DurationRange(this.start, this.end); + + final Duration start; + final Duration end; + + double startFraction(Duration duration) { + return start.inMilliseconds / duration.inMilliseconds; + } + + double endFraction(Duration duration) { + return end.inMilliseconds / duration.inMilliseconds; + } + + @override + String toString() => '$runtimeType(start: $start, end: $end)'; +} diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..bf4d4c03bce3 --- /dev/null +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -0,0 +1,21 @@ +name: video_player_platform_interface +description: A common platform interface for the video_player plugin. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_platform_interface +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 1.0.0 + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^4.1.1 + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart new file mode 100644 index 000000000000..7db8ef6fbe0f --- /dev/null +++ b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart @@ -0,0 +1,273 @@ +// 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 'dart:async'; +import 'dart:ui'; + +import 'package:mockito/mockito.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:video_player_platform_interface/method_channel_video_player.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$VideoPlayerPlatform', () { + test('$MethodChannelVideoPlayer() is the default instance', () { + expect(VideoPlayerPlatform.instance, + isInstanceOf()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + VideoPlayerPlatform.instance = ImplementsVideoPlayerPlatform(); + }, throwsA(isInstanceOf())); + }); + + test('Can be mocked with `implements`', () { + final ImplementsVideoPlayerPlatform mock = + ImplementsVideoPlayerPlatform(); + when(mock.isMock).thenReturn(true); + VideoPlayerPlatform.instance = mock; + }); + + test('Can be extended', () { + VideoPlayerPlatform.instance = ExtendsVideoPlayerPlatform(); + }); + }); + + group('$MethodChannelVideoPlayer', () { + const MethodChannel channel = MethodChannel('flutter.io/videoPlayer'); + final List log = []; + final MethodChannelVideoPlayer player = MethodChannelVideoPlayer(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + }); + }); + + tearDown(() { + log.clear(); + }); + + test('init', () async { + await player.init(); + expect( + log, + [isMethodCall('init', arguments: null)], + ); + }); + + test('dispose', () async { + await player.dispose(1); + expect( + log, + [ + isMethodCall('dispose', arguments: { + 'textureId': 1, + }) + ], + ); + }); + + test('create with asset', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create(DataSource( + sourceType: DataSourceType.asset, + asset: 'someAsset', + package: 'somePackage', + )); + expect( + log, + [ + isMethodCall('create', arguments: { + 'asset': 'someAsset', + 'package': 'somePackage', + }) + ], + ); + expect(textureId, 3); + }); + + test('create with network', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create(DataSource( + sourceType: DataSourceType.network, + uri: 'someUri', + formatHint: VideoFormat.dash, + )); + expect( + log, + [ + isMethodCall('create', arguments: { + 'uri': 'someUri', + 'formatHint': 'dash' + }) + ], + ); + expect(textureId, 3); + }); + + test('create with file', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return {'textureId': 3}; + }); + final int textureId = await player.create(DataSource( + sourceType: DataSourceType.file, + uri: 'someUri', + )); + expect( + log, + [ + isMethodCall('create', arguments: { + 'uri': 'someUri', + }) + ], + ); + expect(textureId, 3); + }); + + test('setLooping', () async { + await player.setLooping(1, true); + expect( + log, + [ + isMethodCall('setLooping', arguments: { + 'textureId': 1, + 'looping': true, + }) + ], + ); + }); + + test('play', () async { + await player.play(1); + expect( + log, + [ + isMethodCall('play', arguments: { + 'textureId': 1, + }) + ], + ); + }); + + test('pause', () async { + await player.pause(1); + expect( + log, + [ + isMethodCall('pause', arguments: { + 'textureId': 1, + }) + ], + ); + }); + + test('setVolume', () async { + await player.setVolume(1, 0.7); + expect( + log, + [ + isMethodCall('setVolume', arguments: { + 'textureId': 1, + 'volume': 0.7, + }) + ], + ); + }); + + test('seekTo', () async { + await player.seekTo(1, const Duration(milliseconds: 12345)); + expect( + log, + [ + isMethodCall('seekTo', arguments: { + 'textureId': 1, + 'location': 12345, + }) + ], + ); + }); + + test('getPosition', () async { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + return 234; + }); + + final Duration position = await player.getPosition(1); + expect( + log, + [ + isMethodCall('position', arguments: { + 'textureId': 1, + }) + ], + ); + expect(position, const Duration(milliseconds: 234)); + }); + + test('videoEventsFor', () async { + // TODO(cbenhagen): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.setMockMessageHandler( + "flutter.io/videoPlayer/videoEvents123", + (ByteData message) async { + final MethodCall methodCall = + const StandardMethodCodec().decodeMethodCall(message); + if (methodCall.method == 'listen') { + // TODO(cbenhagen): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.handlePlatformMessage( + "flutter.io/videoPlayer/videoEvents123", + const StandardMethodCodec() + .encodeSuccessEnvelope({ + 'event': 'initialized', + 'duration': 98765, + 'width': 1920, + 'height': 1080, + }), + (ByteData data) {}); + + return const StandardMethodCodec().encodeSuccessEnvelope(null); + } else if (methodCall.method == 'cancel') { + return const StandardMethodCodec().encodeSuccessEnvelope(null); + } else { + fail('Expected listen or cancel'); + } + }, + ); + final Stream videoEvents = player.videoEventsFor(123); + expect((await videoEvents.first).eventType, VideoEventType.initialized); + expect( + (await videoEvents.first).duration, + const Duration(milliseconds: 98765), + ); + expect( + (await videoEvents.first).size, + const Size(1920, 1080), + ); + }); + }); +} + +class ImplementsVideoPlayerPlatform extends Mock + implements VideoPlayerPlatform {} + +class ExtendsVideoPlayerPlatform extends VideoPlayerPlatform {} diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index cdcb02a02140..1028ee607386 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -14,6 +14,7 @@ readonly EXCLUDED_PLUGINS_LIST=( "instrumentation_adapter" "url_launcher_platform_interface" "google_sign_in_platform_interface" + "video_player_platform_interface" ) # Comma-separated string of the list above readonly EXCLUDED=$(IFS=, ; echo "${EXCLUDED_PLUGINS_LIST[*]}") From dc48e70c7773e3fbe46d86a4bd29df82176d0449 Mon Sep 17 00:00:00 2001 From: xster Date: Thu, 21 Nov 2019 16:08:29 -0800 Subject: [PATCH 25/47] [path_provider] Add v2 embedding support for (#2284) --- packages/path_provider/CHANGELOG.md | 4 ++ packages/path_provider/android/build.gradle | 2 +- .../pathprovider/PathProviderPlugin.java | 48 ++++++++++++------- .../example/android/app/build.gradle | 4 ++ .../java/EmbeddingV1ActivityTest.java | 15 ++++++ .../androidTest/java/MainActivityTest.java | 13 +++++ .../android/app/src/main/AndroidManifest.xml | 6 +++ .../EmbeddingV1Activity.java | 14 ++++++ .../pathproviderexample/MainActivity.java | 15 +++--- .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/path_provider/example/pubspec.yaml | 2 +- packages/path_provider/pubspec.yaml | 5 +- .../path_provider/test/path_provider_e2e.dart | 12 +++++ 13 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 packages/path_provider/example/android/app/src/androidTest/java/EmbeddingV1ActivityTest.java create mode 100644 packages/path_provider/example/android/app/src/androidTest/java/MainActivityTest.java create mode 100644 packages/path_provider/example/android/app/src/main/java/io/flutter/plugins/pathproviderexample/EmbeddingV1Activity.java create mode 100644 packages/path_provider/test/path_provider_e2e.dart diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md index 27dde4284b77..505977e469cf 100644 --- a/packages/path_provider/CHANGELOG.md +++ b/packages/path_provider/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.4.5 + +* Add support for v2 plugins APIs. + ## 1.4.4 * Update driver tests in the example app to e2e tests. diff --git a/packages/path_provider/android/build.gradle b/packages/path_provider/android/build.gradle index 71ea49b2fb39..3bc008e63e0c 100644 --- a/packages/path_provider/android/build.gradle +++ b/packages/path_provider/android/build.gradle @@ -62,4 +62,4 @@ afterEvaluate { } } } -} \ No newline at end of file +} diff --git a/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java b/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java index 30b00d0e5532..0bf9ee4820a3 100644 --- a/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java +++ b/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java @@ -4,9 +4,11 @@ package io.flutter.plugins.pathprovider; +import android.content.Context; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; import androidx.annotation.NonNull; +import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -17,19 +19,33 @@ import java.util.ArrayList; import java.util.List; -public class PathProviderPlugin implements MethodCallHandler { +public class PathProviderPlugin implements FlutterPlugin, MethodCallHandler { - private final Registrar mRegistrar; + private Context context; + private MethodChannel channel; + + public PathProviderPlugin() {} public static void registerWith(Registrar registrar) { - MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/path_provider"); - PathProviderPlugin instance = new PathProviderPlugin(registrar); - channel.setMethodCallHandler(instance); + PathProviderPlugin instance = new PathProviderPlugin(); + instance.channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/path_provider"); + instance.context = registrar.context(); + instance.channel.setMethodCallHandler(instance); } - private PathProviderPlugin(Registrar registrar) { - this.mRegistrar = registrar; + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + channel = + new MethodChannel( + binding.getFlutterEngine().getDartExecutor(), "plugins.flutter.io/path_provider"); + context = binding.getApplicationContext(); + channel.setMethodCallHandler(this); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + channel.setMethodCallHandler(null); + channel = null; } @Override @@ -61,19 +77,19 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { } private String getPathProviderTemporaryDirectory() { - return mRegistrar.context().getCacheDir().getPath(); + return context.getCacheDir().getPath(); } private String getApplicationSupportDirectory() { - return PathUtils.getFilesDir(mRegistrar.context()); + return PathUtils.getFilesDir(context); } private String getPathProviderApplicationDocumentsDirectory() { - return PathUtils.getDataDirectory(mRegistrar.context()); + return PathUtils.getDataDirectory(context); } private String getPathProviderStorageDirectory() { - final File dir = mRegistrar.context().getExternalFilesDir(null); + final File dir = context.getExternalFilesDir(null); if (dir == null) { return null; } @@ -84,13 +100,13 @@ private List getPathProviderExternalCacheDirectories() { final List paths = new ArrayList<>(); if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - for (File dir : mRegistrar.context().getExternalCacheDirs()) { + for (File dir : context.getExternalCacheDirs()) { if (dir != null) { paths.add(dir.getAbsolutePath()); } } } else { - File dir = mRegistrar.context().getExternalCacheDir(); + File dir = context.getExternalCacheDir(); if (dir != null) { paths.add(dir.getAbsolutePath()); } @@ -103,13 +119,13 @@ private List getPathProviderExternalStorageDirectories(String type) { final List paths = new ArrayList<>(); if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { - for (File dir : mRegistrar.context().getExternalFilesDirs(type)) { + for (File dir : context.getExternalFilesDirs(type)) { if (dir != null) { paths.add(dir.getAbsolutePath()); } } } else { - File dir = mRegistrar.context().getExternalFilesDir(type); + File dir = context.getExternalFilesDir(type); if (dir != null) { paths.add(dir.getAbsolutePath()); } diff --git a/packages/path_provider/example/android/app/build.gradle b/packages/path_provider/example/android/app/build.gradle index 2ca6a7a4add3..0404c7203903 100644 --- a/packages/path_provider/example/android/app/build.gradle +++ b/packages/path_provider/example/android/app/build.gradle @@ -54,6 +54,10 @@ flutter { } dependencies { + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/packages/path_provider/example/android/app/src/androidTest/java/EmbeddingV1ActivityTest.java b/packages/path_provider/example/android/app/src/androidTest/java/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..cce04b79f516 --- /dev/null +++ b/packages/path_provider/example/android/app/src/androidTest/java/EmbeddingV1ActivityTest.java @@ -0,0 +1,15 @@ + +package io.flutter.plugins.pathprovider; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import io.flutter.plugins.pathproviderexample.EmbeddingV1Activity; +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/path_provider/example/android/app/src/androidTest/java/MainActivityTest.java b/packages/path_provider/example/android/app/src/androidTest/java/MainActivityTest.java new file mode 100644 index 000000000000..7bdd449981f5 --- /dev/null +++ b/packages/path_provider/example/android/app/src/androidTest/java/MainActivityTest.java @@ -0,0 +1,13 @@ + +package io.flutter.plugins.pathprovider; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import io.flutter.plugins.pathproviderexample.MainActivity; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/path_provider/example/android/app/src/main/AndroidManifest.xml b/packages/path_provider/example/android/app/src/main/AndroidManifest.xml index 793c13b6e612..9e03a9373e33 100644 --- a/packages/path_provider/example/android/app/src/main/AndroidManifest.xml +++ b/packages/path_provider/example/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,12 @@ + + homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider -version: 1.4.4 +version: 1.4.5 flutter: plugin: @@ -18,6 +18,7 @@ dependencies: meta: ^1.0.5 dev_dependencies: + e2e: ^0.2.1 flutter_test: sdk: flutter flutter_driver: @@ -27,4 +28,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=0.1.4 <2.0.0" + flutter: ">=1.9.1+hotfix.5 <2.0.0" diff --git a/packages/path_provider/test/path_provider_e2e.dart b/packages/path_provider/test/path_provider_e2e.dart new file mode 100644 index 000000000000..545671e32b01 --- /dev/null +++ b/packages/path_provider/test/path_provider_e2e.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Can get temporary directory', (WidgetTester tester) async { + final String tempPath = (await getTemporaryDirectory()).path; + expect(tempPath, isNotEmpty); + }); +} From 95594f5c40ae59aad98938fef3e71fc0d83c2798 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Thu, 21 Nov 2019 17:43:18 -0800 Subject: [PATCH 26/47] Fix testing instructions in CONTRIBUTING.md (#2222) * Fix testing instructions in CONTRIBUTING.md --- CONTRIBUTING.md | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6e3e957af29..493bf7f55c25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,26 +42,46 @@ USB and debugging enabled on that device. ## Running the tests -Flutter plugins have both unit tests of their Dart API and integration tests that run on a virtual or actual device. +### Integration tests -To run the unit tests: +To run the integration tests using Flutter driver: -``` -flutter test test/_test.dart +```console +cd example +flutter drive test_driver/.dart ``` -To run the integration tests using Flutter driver: +To run integration tests as instrumentation tests on a local Android device: -``` +```console cd example -flutter drive test/.dart +flutter build apk +cd android && ./gradlew -Ptarget=$(pwd)/../test_driver/_test.dart app:connectedAndroidTest ``` -To run integration tests as instrumentation tests on a local Android device: +These tests may also be in folders just named "test," or have filenames ending +with "e2e". +### Dart unit tests + +To run the unit tests: + +```console +flutter test test/_test.dart ``` + +### Java unit tests + +These can be ran through Android Studio once the example app is opened as an +Android project. + +Without Android Studio, they can be ran through the terminal. + +```console cd example -(cd android && ./gradlew -Ptarget=$(pwd)/../test_live/_test.dart connectedAndroidTest) +flutter build apk +cd android +./gradlew test ``` ## Contributing code From 95873dcbea08fcb5e79a8cd40428303dd9d98261 Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 21 Nov 2019 17:45:17 -0800 Subject: [PATCH 27/47] Update CONTRIBUTING.md (#2292) Removes obsolete recommendations about removing mockito (which is now used in testing federated plugins, see https://github.com/FirebaseExtended/flutterfire/pull/1472). --- CONTRIBUTING.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 493bf7f55c25..7edb13c08e60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,13 +121,6 @@ Plugins tests are run automatically on contributions using Cirrus CI. However, d cost constraints, pull requests from non-committers may not run all the tests automatically. -The plugins team prefers that unit tests are written using `setMockMethodCallHandler` -rather than using mockito to mock out `MethodChannel`. For a list of the plugins that -are still using the mockito testing style and need to be converted, see -[issue 34284](https://github.com/flutter/flutter/issues/34284). If you are contributing -tests to an existing plugin that uses mockito `MethodChannel`, consider converting -them to use `setMockMethodCallHandler` instead. - Once you've gotten an LGTM from a project maintainer and once your PR has received the green light from all our automated testing, wait for one the package maintainers to merge the pull request and `pub submit` any affected packages. From db123e8d5da6edf75efb1168e16cc3e5329d2ddc Mon Sep 17 00:00:00 2001 From: Chris Yang Date: Thu, 21 Nov 2019 21:30:27 -0800 Subject: [PATCH 28/47] [image_picker]fix a crash when a non-image file is picked. (#2293) --- packages/image_picker/CHANGELOG.md | 7 +- .../imagepicker/ImagePickerDelegate.java | 5 +- .../plugins/imagepicker/ImageResizer.java | 80 +++++++++++++----- .../example/android/app/build.gradle | 1 + .../imagepicker/ImagePickerCacheTest.java | 3 - .../imagepicker/ImagePickerDelegateTest.java | 14 +-- .../plugins/imagepicker/ImageResizerTest.java | 65 ++++++++++++++ .../app/src/test/resources/pngImage.png | Bin 0 -> 629 bytes packages/image_picker/pubspec.yaml | 2 +- 9 files changed, 140 insertions(+), 37 deletions(-) create mode 100644 packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java create mode 100644 packages/image_picker/example/android/app/src/test/resources/pngImage.png diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 11c48e8724bc..96bb4fcad8c3 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md @@ -1,6 +1,11 @@ +## 0.6.2+1 + +* Android: Fix a crash when a non-image file is picked. +* Android: Fix unwanted bitmap scaling. + ## 0.6.2 -* iOS: Fixes an issue where picking conent from Gallery would result in a crash on iOS 13. +* iOS: Fixes an issue where picking content from Gallery would result in a crash on iOS 13. ## 0.6.1+11 diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java index f9318e9c5760..e34a3b5632c0 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerDelegate.java @@ -521,10 +521,7 @@ private void handleImageResult(String path, boolean shouldDeleteOriginalIfScaled if (methodCall != null) { Double maxWidth = methodCall.argument("maxWidth"); Double maxHeight = methodCall.argument("maxHeight"); - int imageQuality = - methodCall.argument("imageQuality") == null - ? 100 - : (int) methodCall.argument("imageQuality"); + Integer imageQuality = methodCall.argument("imageQuality"); String finalImagePath = imageResizer.resizeImageIfNeeded(path, maxWidth, maxHeight, imageQuality); diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java index ab3120afb6d0..ca0498bfa55b 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImageResizer.java @@ -7,6 +7,7 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; +import androidx.annotation.Nullable; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -28,31 +29,39 @@ class ImageResizer { *

If no resizing is needed, returns the path for the original image. */ String resizeImageIfNeeded( - String imagePath, Double maxWidth, Double maxHeight, int imageQuality) { + String imagePath, + @Nullable Double maxWidth, + @Nullable Double maxHeight, + @Nullable Integer imageQuality) { boolean shouldScale = - maxWidth != null || maxHeight != null || (imageQuality > -1 && imageQuality < 101); - - if (!shouldScale) { - return imagePath; + maxWidth != null || maxHeight != null || isImageQualityValid(imageQuality); + String[] pathParts = imagePath.split("/"); + String imageName = pathParts[pathParts.length - 1]; + File file; + Bitmap bmp = decodeFile(imagePath); + if (bmp == null) { + return null; } - try { - File scaledImage = resizedImage(imagePath, maxWidth, maxHeight, imageQuality); - exifDataCopier.copyExif(imagePath, scaledImage.getPath()); - - return scaledImage.getPath(); + if (!shouldScale) { + file = createImageOnExternalDirectory(imageName, bmp, 100); + } else { + file = resizedImage(bmp, maxWidth, maxHeight, imageQuality, imageName); + } + copyExif(imagePath, file.getPath()); + return file.getPath(); } catch (IOException e) { throw new RuntimeException(e); } } - private File resizedImage(String path, Double maxWidth, Double maxHeight, int imageQuality) + private File resizedImage( + Bitmap bmp, Double maxWidth, Double maxHeight, Integer imageQuality, String outputImageName) throws IOException { - Bitmap bmp = BitmapFactory.decodeFile(path); double originalWidth = bmp.getWidth() * 1.0; double originalHeight = bmp.getHeight() * 1.0; - if (imageQuality < 0 || imageQuality > 100) { + if (!isImageQualityValid(imageQuality)) { imageQuality = 100; } @@ -91,24 +100,51 @@ private File resizedImage(String path, Double maxWidth, Double maxHeight, int im } } - Bitmap scaledBmp = Bitmap.createScaledBitmap(bmp, width.intValue(), height.intValue(), false); + Bitmap scaledBmp = createScaledBitmap(bmp, width.intValue(), height.intValue(), false); + File file = + createImageOnExternalDirectory("/scaled_" + outputImageName, scaledBmp, imageQuality); + return file; + } + + private File createFile(File externalFilesDirectory, String child) { + return new File(externalFilesDirectory, child); + } + + private FileOutputStream createOutputStream(File imageFile) throws IOException { + return new FileOutputStream(imageFile); + } + + private void copyExif(String filePathOri, String filePathDest) { + exifDataCopier.copyExif(filePathOri, filePathDest); + } + + private Bitmap decodeFile(String path) { + return BitmapFactory.decodeFile(path); + } + + private Bitmap createScaledBitmap(Bitmap bmp, int width, int height, boolean filter) { + return Bitmap.createScaledBitmap(bmp, width, height, filter); + } + + private boolean isImageQualityValid(Integer imageQuality) { + return imageQuality != null && imageQuality > 0 && imageQuality < 100; + } + + private File createImageOnExternalDirectory(String name, Bitmap bitmap, int imageQuality) + throws IOException { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - boolean saveAsPNG = bmp.hasAlpha(); + boolean saveAsPNG = bitmap.hasAlpha(); if (saveAsPNG) { Log.d( "ImageResizer", "image_picker: compressing is not supported for type PNG. Returning the image with original quality"); } - scaledBmp.compress( + bitmap.compress( saveAsPNG ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG, imageQuality, outputStream); - - String[] pathParts = path.split("/"); - String imageName = pathParts[pathParts.length - 1]; - - File imageFile = new File(externalFilesDirectory, "/scaled_" + imageName); - FileOutputStream fileOutput = new FileOutputStream(imageFile); + File imageFile = createFile(externalFilesDirectory, name); + FileOutputStream fileOutput = createOutputStream(imageFile); fileOutput.write(outputStream.toByteArray()); fileOutput.close(); return imageFile; diff --git a/packages/image_picker/example/android/app/build.gradle b/packages/image_picker/example/android/app/build.gradle index 800e3e836a97..483268fdc18c 100755 --- a/packages/image_picker/example/android/app/build.gradle +++ b/packages/image_picker/example/android/app/build.gradle @@ -62,4 +62,5 @@ dependencies { testImplementation 'org.mockito:mockito-core:2.17.0' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + testImplementation "org.robolectric:robolectric:3.3.2" } diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java index 8e89a15abc8e..51733a503a92 100644 --- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java +++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerCacheTest.java @@ -25,10 +25,7 @@ import org.mockito.MockitoAnnotations; public class ImagePickerCacheTest { - private static final double WIDTH = 10.0; - private static final double HEIGHT = 10.0; private static final int IMAGE_QUALITY = 90; - private static final String PATH = "a_mock_path"; @Mock Activity mockActivity; @Mock SharedPreferences mockPreference; diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java index 60e1167cd87a..88fa3372766f 100644 --- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java +++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerDelegateTest.java @@ -24,9 +24,9 @@ import org.mockito.MockitoAnnotations; public class ImagePickerDelegateTest { - private static final double WIDTH = 10.0; - private static final double HEIGHT = 10.0; - private static final int IMAGE_QUALITY = 100; + private static final Double WIDTH = 10.0; + private static final Double HEIGHT = 10.0; + private static final Integer IMAGE_QUALITY = 90; @Mock Activity mockActivity; @Mock ImageResizer mockImageResizer; @@ -62,13 +62,15 @@ public void setUp() { when(mockFileUtils.getPathFromUri(any(Context.class), any(Uri.class))) .thenReturn("pathFromUri"); + when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, null)) + .thenReturn("originalPath"); when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, null, IMAGE_QUALITY)) .thenReturn("originalPath"); - when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, IMAGE_QUALITY)) + when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, HEIGHT, null)) .thenReturn("scaledPath"); - when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, IMAGE_QUALITY)) + when(mockImageResizer.resizeImageIfNeeded("pathFromUri", WIDTH, null, null)) .thenReturn("scaledPath"); - when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, IMAGE_QUALITY)) + when(mockImageResizer.resizeImageIfNeeded("pathFromUri", null, HEIGHT, null)) .thenReturn("scaledPath"); mockFileUriResolver = new MockFileUriResolver(); diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java new file mode 100644 index 000000000000..aac7472eaac3 --- /dev/null +++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImageResizerTest.java @@ -0,0 +1,65 @@ +// 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. + +package io.flutter.plugins.imagepicker; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import java.io.File; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +// RobolectricTestRunner always creates a default mock bitmap when reading from file. So we cannot actually test the scaling. +// But we can still test whether the original or scaled file is created. +@RunWith(RobolectricTestRunner.class) +public class ImageResizerTest { + + ImageResizer resizer; + File imageFile; + File externalDirectory; + Bitmap originalImageBitmap; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + imageFile = new File(getClass().getClassLoader().getResource("pngImage.png").getFile()); + originalImageBitmap = BitmapFactory.decodeFile(imageFile.getPath()); + TemporaryFolder temporaryFolder = new TemporaryFolder(); + temporaryFolder.create(); + externalDirectory = temporaryFolder.newFolder("image_picker_testing_path"); + resizer = new ImageResizer(externalDirectory, new ExifDataCopier()); + } + + @Test + public void onResizeImageIfNeeded_WhenQualityIsNull_ShoultNotResize_ReturnTheUnscaledFile() { + String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, null); + assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/pngImage.png")); + } + + @Test + public void onResizeImageIfNeeded_WhenQualityIsNotNull_ShoulResize_ReturnResizedFile() { + String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, null, 50); + assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); + } + + @Test + public void onResizeImageIfNeeded_WhenWidthIsNotNull_ShoulResize_ReturnResizedFile() { + String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), 50.0, null, null); + assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); + } + + @Test + public void onResizeImageIfNeeded_WhenHeightIsNotNull_ShoulResize_ReturnResizedFile() { + String outoutFile = resizer.resizeImageIfNeeded(imageFile.getPath(), null, 50.0, null); + assertThat(outoutFile, equalTo(externalDirectory.getPath() + "/scaled_pngImage.png")); + } +} diff --git a/packages/image_picker/example/android/app/src/test/resources/pngImage.png b/packages/image_picker/example/android/app/src/test/resources/pngImage.png new file mode 100644 index 0000000000000000000000000000000000000000..22ac5a5a1485e2df792d72317d11e7c543bca4de GIT binary patch literal 629 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5l;AAzh%9DcU>62q#&Zh% z96-Y_WrjqQMELqxCFkerC8p#jrRr7W764TzC zHb_`sNdc^+B->Ug!Z$#{Ilm}X!Bo#g&p^qJOF==wrYI%ND#*nRsvXF)RmvzSDX`Ml zFE20GD>v55FG|-pw6wI;H!#vSGSUUA&@HaaD@m--%_~-h7y>iLCAB!YD6^m>Ge1uO zWNu(Fge&DHI?<3>F23u^pF - Rhodes Davis Jr. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.6.2 +version: 0.6.2+1 flutter: plugin: From 56b81b7b7e711397c9e3c0c050878fc09fc80fc0 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Fri, 22 Nov 2019 12:14:55 -0800 Subject: [PATCH 29/47] [video_player] Improve DartDocs and test coverage (#2286) Also adds a lint to prevent undocumented APIs going forward. --- .../video_player/video_player/CHANGELOG.md | 4 + .../video_player/analysis_options.yaml | 11 + .../video_player/example/lib/main.dart | 2 + .../video_player/lib/video_player.dart | 151 +++++++- .../video_player/video_player/pubspec.yaml | 4 +- .../video_player/test/video_player_test.dart | 358 ++++++++++++++++-- 6 files changed, 494 insertions(+), 36 deletions(-) create mode 100644 packages/video_player/video_player/analysis_options.yaml diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 1bac34f6649b..d4824c461a55 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.3+3 + +* Add DartDocs and unit tests. + ## 0.10.3+2 * Update the homepage to point to the new plugin location diff --git a/packages/video_player/video_player/analysis_options.yaml b/packages/video_player/video_player/analysis_options.yaml new file mode 100644 index 000000000000..6c7fd57bb022 --- /dev/null +++ b/packages/video_player/video_player/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index 43d9355c4345..f55d3f7dd593 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 + /// An example of using the plugin, controlling lifecycle and playback of the /// video. diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index f1b0e7c9791d..f92693e79eda 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -14,16 +14,46 @@ final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer') // performed. ..invokeMethod('init'); +/// Describes a discrete segment of time within a video using a [start] and +/// [end] [Duration]. class DurationRange { + /// Trusts that the given [start] and [end] are actually in order. They should + /// both be non-null. DurationRange(this.start, this.end); + /// The beginning of the segment described relative to the beginning of the + /// entire video. Should be shorter than or equal to [end]. + /// + /// For example, if the entire video is 4 minutes long and the range is from + /// 1:00-2:00, this should be a `Duration` of one minute. final Duration start; + + /// The end of the segment described as a duration relative to the beginning of + /// the entire video. This is expected to be non-null and longer than or equal + /// to [start]. + /// + /// For example, if the entire video is 4 minutes long and the range is from + /// 1:00-2:00, this should be a `Duration` of two minutes. final Duration end; + /// Assumes that [duration] is the total length of the video that this + /// DurationRange is a segment form. It returns the percentage that [start] is + /// through the entire video. + /// + /// For example, assume that the entire video is 4 minutes long. If [start] has + /// a duration of one minute, this will return `0.25` since the DurationRange + /// starts 25% of the way through the video's total length. double startFraction(Duration duration) { return start.inMilliseconds / duration.inMilliseconds; } + /// Assumes that [duration] is the total length of the video that this + /// DurationRange is a segment form. It returns the percentage that [start] is + /// through the entire video. + /// + /// For example, assume that the entire video is 4 minutes long. If [end] has a + /// duration of two minutes, this will return `0.5` since the DurationRange + /// ends 50% of the way through the video's total length. double endFraction(Duration duration) { return end.inMilliseconds / duration.inMilliseconds; } @@ -32,11 +62,26 @@ class DurationRange { String toString() => '$runtimeType(start: $start, end: $end)'; } -enum VideoFormat { dash, hls, ss, other } +/// The file format of the given video. +enum VideoFormat { + /// Dynamic Adaptive Streaming over HTTP, also known as MPEG-DASH. + dash, + + /// HTTP Live Streaming. + hls, + + /// Smooth Streaming. + ss, + + /// Any format other than the other ones defined in this enum. + other +} /// The duration, current position, buffering state, error state and settings /// of a [VideoPlayerController]. class VideoPlayerValue { + /// Constructs a video with the given values. Only [duration] is required. The + /// rest will initialize with default values when unset. VideoPlayerValue({ @required this.duration, this.size, @@ -49,8 +94,11 @@ class VideoPlayerValue { this.errorDescription, }); + /// Returns an instance with a `null` [Duration]. VideoPlayerValue.uninitialized() : this(duration: null); + /// Returns an instance with a `null` [Duration] and the given + /// [errorDescription]. VideoPlayerValue.erroneous(String errorDescription) : this(duration: null, errorDescription: errorDescription); @@ -87,10 +135,19 @@ class VideoPlayerValue { /// Is null when [initialized] is false. final Size size; + /// Indicates whether or not the video has been loaded and is ready to play. bool get initialized => duration != null; + + /// Indicates whether or not the video is in an error state. If this is true + /// [errorDescription] should have information about the problem. bool get hasError => errorDescription != null; + + /// Returns [size.width] / [size.height] when size is non-null, or `1.0.` when + /// it is. double get aspectRatio => size != null ? size.width / size.height : 1.0; + /// Returns a new instance that has the same values as this current instance, + /// except for any overrides passed in as arguments to [copyWidth]. VideoPlayerValue copyWith({ Duration duration, Size size, @@ -130,7 +187,19 @@ class VideoPlayerValue { } } -enum DataSourceType { asset, network, file } +/// The way in which the video was originally loaded. This has nothing to do +/// with the video's file type. It's just the place from which the video is +/// fetched from. +enum DataSourceType { + /// The video was included in the app's asset files. + asset, + + /// The video was downloaded from the internet. + network, + + /// The video was loaded off of the local filesystem. + file +} /// Controls a platform video player, and provides updates when the state is /// changing. @@ -177,13 +246,20 @@ class VideoPlayerController extends ValueNotifier { super(VideoPlayerValue(duration: null)); int _textureId; + + /// The URI to the video file. This will be in different formats depending on + /// the [DataSourceType] of the original video. final String dataSource; + + /// **Android only**. Will override the platform's generic file format + /// detection with whatever is set here. final VideoFormat formatHint; /// Describes the type of data source this [VideoPlayerController] /// is constructed with. final DataSourceType dataSourceType; + /// Only set for [asset] videos. The package that the asset was loaded from. final String package; Timer _timer; bool _isDisposed = false; @@ -191,9 +267,12 @@ class VideoPlayerController extends ValueNotifier { StreamSubscription _eventSubscription; _VideoAppLifeCycleObserver _lifeCycleObserver; + /// This is just exposed for testing. It shouldn't be used by anyone depending + /// on the plugin. @visibleForTesting int get textureId => _textureId; + /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { _lifeCycleObserver = _VideoAppLifeCycleObserver(this); _lifeCycleObserver.initialize(); @@ -305,16 +384,24 @@ class VideoPlayerController extends ValueNotifier { super.dispose(); } + /// Starts playing the video. + /// + /// This method returns a future that completes as soon as the "play" command + /// has been sent to the platform, not when playback itself is totally + /// finished. Future play() async { value = value.copyWith(isPlaying: true); await _applyPlayPause(); } + /// Sets whether or not the video should loop after playing once. See also + /// [VideoPlayerValue.isLooping]. Future setLooping(bool looping) async { value = value.copyWith(isLooping: looping); await _applyLooping(); } + /// Pauses the video. Future pause() async { value = value.copyWith(isPlaying: false); await _applyPlayPause(); @@ -384,6 +471,11 @@ class VideoPlayerController extends ValueNotifier { ); } + /// Sets the video's current timestamp to be at [moment]. The next + /// time the video is played it will resume from the given [moment]. + /// + /// If [moment] is outside of the video's full range it will be automatically + /// and silently clamped. Future seekTo(Duration moment) async { if (_isDisposed) { return; @@ -449,10 +541,13 @@ class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { } } -/// Displays the video controlled by [controller]. +/// Widget that displays the video controlled by [controller]. class VideoPlayer extends StatefulWidget { + /// Uses the given [controller] for all video rendered in this widget. VideoPlayer(this.controller); + /// The [VideoPlayerController] responsible for the video being rendered in + /// this widget. final VideoPlayerController controller; @override @@ -503,15 +598,43 @@ class _VideoPlayerState extends State { } } +/// Used to configure the [VideoProgressIndicator] widget's colors for how it +/// describes the video's status. +/// +/// The widget uses default colors that are customizeable through this class. class VideoProgressColors { + /// Any property can be set to any color. They each have defaults. + /// + /// [playedColor] defaults to red at 70% opacity. This fills up a portion of + /// the [VideoProgressIndicator] to represent how much of the video has played + /// so far. + /// + /// [bufferedColor] defaults to blue at 20% opacity. This fills up a portion + /// of [VideoProgressIndicator] to represent how much of the video has + /// buffered so far. + /// + /// [backgroundColor] defaults to gray at 50% opacity. This is the background + /// color behind both [playedColor] and [bufferedColor] to denote the total + /// size of the video compared to either of those values. VideoProgressColors({ this.playedColor = const Color.fromRGBO(255, 0, 0, 0.7), this.bufferedColor = const Color.fromRGBO(50, 50, 200, 0.2), this.backgroundColor = const Color.fromRGBO(200, 200, 200, 0.5), }); + /// [playedColor] defaults to red at 70% opacity. This fills up a portion of + /// the [VideoProgressIndicator] to represent how much of the video has played + /// so far. final Color playedColor; + + /// [bufferedColor] defaults to blue at 20% opacity. This fills up a portion + /// of [VideoProgressIndicator] to represent how much of the video has + /// buffered so far. final Color bufferedColor; + + /// [backgroundColor] defaults to gray at 50% opacity. This is the background + /// color behind both [playedColor] and [bufferedColor] to denote the total + /// size of the video compared to either of those values. final Color backgroundColor; } @@ -584,6 +707,12 @@ class _VideoScrubberState extends State<_VideoScrubber> { /// [padding] allows to specify some extra padding around the progress indicator /// that will also detect the gestures. class VideoProgressIndicator extends StatefulWidget { + /// Construct an instance that displays the play/buffering status of the video + /// controlled by [controller]. + /// + /// Defaults will be used for everything except [controller] if they're not + /// provided. [allowScrubbing] defaults to false, and [padding] will default + /// to `top: 5.0`. VideoProgressIndicator( this.controller, { VideoProgressColors colors, @@ -591,9 +720,25 @@ class VideoProgressIndicator extends StatefulWidget { this.padding = const EdgeInsets.only(top: 5.0), }) : colors = colors ?? VideoProgressColors(); + /// The [VideoPlayerController] that actually associates a video with this + /// widget. final VideoPlayerController controller; + + /// The default colors used throughout the indicator. + /// + /// See [VideoProgressColors] for default values. final VideoProgressColors colors; + + /// When true, the widget will detect touch input and try to seek the video + /// accordingly. The widget ignores such input when false. + /// + /// Defaults to false. final bool allowScrubbing; + + /// This allows for visual padding around the progress indicator that can + /// still detect gestures via [allowScrubbing]. + /// + /// Defaults to `top: 5.0`. final EdgeInsets padding; @override diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 32d1e98e586c..cf85568de389 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -2,8 +2,8 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.3+2 -homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player +version: 0.10.3+3 +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: plugin: 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 9211f0c3a87c..10b5754bbad2 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -90,56 +90,339 @@ void main() { fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); }); - test('initialize asset', () async { - final VideoPlayerController controller = VideoPlayerController.asset( - 'a.avi', + group('initialize', () { + test('asset', () async { + final VideoPlayerController controller = VideoPlayerController.asset( + 'a.avi', + ); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], + { + 'asset': 'a.avi', + 'package': null, + }); + }); + + test('network', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], + { + 'uri': 'https://127.0.0.1', + 'formatHint': null, + }); + }); + + test('network with hint', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + formatHint: VideoFormat.dash); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], + { + 'uri': 'https://127.0.0.1', + 'formatHint': 'dash', + }); + }); + + test('file', () async { + final VideoPlayerController controller = + VideoPlayerController.file(File('a.avi')); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], + { + 'uri': 'file://a.avi', + }); + }); + }); + + test('dispose', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + expect(controller.textureId, isNull); + expect(await controller.position, const Duration(seconds: 0)); + controller.initialize(); + + await controller.dispose(); + + expect(controller.textureId, isNotNull); + expect(await controller.position, isNull); + }); + + test('play', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', ); await controller.initialize(); + expect(controller.value.isPlaying, isFalse); + await controller.play(); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0], { - 'asset': 'a.avi', - 'package': null, - }); + expect(controller.value.isPlaying, isTrue); + expect(fakeVideoPlayerPlatform.calls.last.method, 'play'); }); - test('initialize network', () async { + test('setLooping', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', ); await controller.initialize(); + expect(controller.value.isLooping, isFalse); + await controller.setLooping(true); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0], { - 'uri': 'https://127.0.0.1', - 'formatHint': null, - }); + expect(controller.value.isLooping, isTrue); }); - test('initialize network with hint', () async { + test('pause', () async { final VideoPlayerController controller = VideoPlayerController.network( - 'https://127.0.0.1', - formatHint: VideoFormat.dash); + 'https://127.0.0.1', + ); await controller.initialize(); + await controller.play(); + expect(controller.value.isPlaying, isTrue); + + await controller.pause(); + + expect(controller.value.isPlaying, isFalse); + expect(fakeVideoPlayerPlatform.calls.last.method, 'pause'); + }); + + group('seekTo', () { + test('works', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(await controller.position, const Duration(seconds: 0)); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0], { - 'uri': 'https://127.0.0.1', - 'formatHint': 'dash', + await controller.seekTo(const Duration(milliseconds: 500)); + + expect(await controller.position, const Duration(milliseconds: 500)); + }); + + test('clamps values that are too high or low', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(await controller.position, const Duration(seconds: 0)); + + await controller.seekTo(const Duration(seconds: 100)); + expect(await controller.position, const Duration(seconds: 1)); + + await controller.seekTo(const Duration(seconds: -100)); + expect(await controller.position, const Duration(seconds: 0)); }); }); - test('initialize file', () async { - final VideoPlayerController controller = - VideoPlayerController.file(File('a.avi')); - await controller.initialize(); + group('setVolume', () { + test('works', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.volume, 1.0); - expect( - fakeVideoPlayerPlatform.dataSourceDescriptions[0], { - 'uri': 'file://a.avi', + const double volume = 0.5; + await controller.setVolume(volume); + + expect(controller.value.volume, volume); + }); + + test('clamps values that are too high or low', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.volume, 1.0); + + await controller.setVolume(-1); + expect(controller.value.volume, 0.0); + + await controller.setVolume(11); + expect(controller.value.volume, 1.0); + }); + }); + + group('Platform callbacks', () { + testWidgets('playing completed', (WidgetTester tester) async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.isPlaying, isFalse); + await controller.play(); + expect(controller.value.isPlaying, isTrue); + final FakeVideoEventStream fakeVideoEventStream = + fakeVideoPlayerPlatform.streams[controller.textureId]; + assert(fakeVideoEventStream != null); + + fakeVideoEventStream.eventsChannel + .sendEvent({'event': 'completed'}); + await tester.pumpAndSettle(); + + expect(controller.value.isPlaying, isFalse); + expect(controller.value.position, controller.value.duration); + }); + + testWidgets('buffering status', (WidgetTester tester) async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + expect(controller.value.isBuffering, false); + expect(controller.value.buffered, isEmpty); + final FakeVideoEventStream fakeVideoEventStream = + fakeVideoPlayerPlatform.streams[controller.textureId]; + assert(fakeVideoEventStream != null); + + fakeVideoEventStream.eventsChannel + .sendEvent({'event': 'bufferingStart'}); + await tester.pumpAndSettle(); + expect(controller.value.isBuffering, isTrue); + + const Duration bufferStart = Duration(seconds: 0); + const Duration bufferEnd = Duration(milliseconds: 500); + fakeVideoEventStream.eventsChannel.sendEvent({ + 'event': 'bufferingUpdate', + 'values': >[ + [bufferStart.inMilliseconds, bufferEnd.inMilliseconds] + ], + }); + await tester.pumpAndSettle(); + expect(controller.value.isBuffering, isTrue); + expect(controller.value.buffered.length, 1); + expect(controller.value.buffered[0].toString(), + DurationRange(bufferStart, bufferEnd).toString()); + + fakeVideoEventStream.eventsChannel + .sendEvent({'event': 'bufferingEnd'}); + await tester.pumpAndSettle(); + expect(controller.value.isBuffering, isFalse); }); }); }); + + group('DurationRange', () { + test('uses given values', () { + const Duration start = Duration(seconds: 2); + const Duration end = Duration(seconds: 8); + + final DurationRange range = DurationRange(start, end); + + expect(range.start, start); + expect(range.end, end); + expect(range.toString(), contains('start: $start, end: $end')); + }); + + test('calculates fractions', () { + const Duration start = Duration(seconds: 2); + const Duration end = Duration(seconds: 8); + const Duration total = Duration(seconds: 10); + + final DurationRange range = DurationRange(start, end); + + expect(range.startFraction(total), .2); + expect(range.endFraction(total), .8); + }); + }); + + group('VideoPlayerValue', () { + test('uninitialized()', () { + final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized(); + + expect(uninitialized.duration, isNull); + expect(uninitialized.position, equals(const Duration(seconds: 0))); + expect(uninitialized.buffered, isEmpty); + expect(uninitialized.isPlaying, isFalse); + expect(uninitialized.isLooping, isFalse); + expect(uninitialized.isBuffering, isFalse); + expect(uninitialized.volume, 1.0); + expect(uninitialized.errorDescription, isNull); + expect(uninitialized.size, isNull); + expect(uninitialized.size, isNull); + expect(uninitialized.initialized, isFalse); + expect(uninitialized.hasError, isFalse); + expect(uninitialized.aspectRatio, 1.0); + }); + + test('erroneous()', () { + const String errorMessage = 'foo'; + final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage); + + expect(error.duration, isNull); + expect(error.position, equals(const Duration(seconds: 0))); + expect(error.buffered, isEmpty); + expect(error.isPlaying, isFalse); + expect(error.isLooping, isFalse); + expect(error.isBuffering, isFalse); + expect(error.volume, 1.0); + expect(error.errorDescription, errorMessage); + expect(error.size, isNull); + expect(error.size, isNull); + expect(error.initialized, isFalse); + expect(error.hasError, isTrue); + expect(error.aspectRatio, 1.0); + }); + + test('toString()', () { + const Duration duration = Duration(seconds: 5); + const Size size = Size(400, 300); + const Duration position = Duration(seconds: 1); + final List buffered = [ + DurationRange(const Duration(seconds: 0), const Duration(seconds: 4)) + ]; + const bool isPlaying = true; + const bool isLooping = true; + const bool isBuffering = true; + const double volume = 0.5; + + final VideoPlayerValue value = VideoPlayerValue( + duration: duration, + size: size, + position: position, + buffered: buffered, + isPlaying: isPlaying, + isLooping: isLooping, + isBuffering: isBuffering, + volume: volume); + + expect(value.toString(), + 'VideoPlayerValue(duration: 0:00:05.000000, size: Size(400.0, 300.0), position: 0:00:01.000000, buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:04.000000)], isPlaying: true, isLooping: true, isBuffering: truevolume: 0.5, errorDescription: null)'); + }); + + test('copyWith()', () { + final VideoPlayerValue original = VideoPlayerValue.uninitialized(); + final VideoPlayerValue exactCopy = original.copyWith(); + + expect(exactCopy.toString(), original.toString()); + }); + }); + + test('VideoProgressColors', () { + const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75); + const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5); + const Color backgroundColor = Color.fromRGBO(255, 255, 0, 0.25); + + final VideoProgressColors colors = VideoProgressColors( + playedColor: playedColor, + bufferedColor: bufferedColor, + backgroundColor: backgroundColor); + + expect(colors.playedColor, playedColor); + expect(colors.bufferedColor, bufferedColor); + expect(colors.backgroundColor, backgroundColor); + }); } class FakeVideoPlayerPlatform { @@ -150,16 +433,20 @@ class FakeVideoPlayerPlatform { final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer'); Completer initialized = Completer(); + List calls = []; List> dataSourceDescriptions = >[]; + final Map streams = {}; int nextTextureId = 0; + final Map _positions = {}; Future onMethodCall(MethodCall call) { + calls.add(call); switch (call.method) { case 'init': initialized.complete(true); break; case 'create': - FakeVideoEventStream( + streams[nextTextureId] = FakeVideoEventStream( nextTextureId, 100, 100, const Duration(seconds: 1)); final Map dataSource = call.arguments; dataSourceDescriptions.add(dataSource.cast()); @@ -169,11 +456,20 @@ class FakeVideoPlayerPlatform { }; }); break; - case 'setLooping': + case 'position': + final Duration position = _positions[call.arguments['textureId']] ?? + const Duration(seconds: 0); + return Future.value(position.inMilliseconds); break; - case 'setVolume': + case 'seekTo': + _positions[call.arguments['textureId']] = + Duration(milliseconds: call.arguments['location']); break; + case 'dispose': case 'pause': + case 'play': + case 'setLooping': + case 'setVolume': break; default: throw UnimplementedError( From 3a87296a40a2624d200917d58f036baa9fb18df8 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Fri, 22 Nov 2019 14:12:46 -0800 Subject: [PATCH 30/47] [shared_preferences] Add missing DartDoc (#2296) Also adds a lint to prevent further undocumented methods. --- packages/shared_preferences/CHANGELOG.md | 8 ++++++-- packages/shared_preferences/analysis_options.yaml | 11 +++++++++++ packages/shared_preferences/example/lib/main.dart | 2 ++ .../shared_preferences/lib/shared_preferences.dart | 5 +++++ packages/shared_preferences/pubspec.yaml | 2 +- 5 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 packages/shared_preferences/analysis_options.yaml diff --git a/packages/shared_preferences/CHANGELOG.md b/packages/shared_preferences/CHANGELOG.md index 1cc5c511c8d2..083d2b9b658a 100644 --- a/packages/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/CHANGELOG.md @@ -1,7 +1,11 @@ +## 0.5.4+6 + +* Add missing documentation and a lint to prevent further undocumented APIs. + ## 0.5.4+5 -* Update and migrate iOS example project by removing flutter_assets, change - "English" to "en", remove extraneous xcconfigs and framework outputs, +* Update and migrate iOS example project by removing flutter_assets, change + "English" to "en", remove extraneous xcconfigs and framework outputs, update to Xcode 11 build settings, and remove ARCHS. ## 0.5.4+4 diff --git a/packages/shared_preferences/analysis_options.yaml b/packages/shared_preferences/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/shared_preferences/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/shared_preferences/example/lib/main.dart b/packages/shared_preferences/example/lib/main.dart index 0d93675bc84d..5c7f0b0fda12 100644 --- a/packages/shared_preferences/example/lib/main.dart +++ b/packages/shared_preferences/example/lib/main.dart @@ -2,6 +2,8 @@ // 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'; diff --git a/packages/shared_preferences/lib/shared_preferences.dart b/packages/shared_preferences/lib/shared_preferences.dart index 3fe176f41f25..c82116a57437 100644 --- a/packages/shared_preferences/lib/shared_preferences.dart +++ b/packages/shared_preferences/lib/shared_preferences.dart @@ -19,6 +19,11 @@ class SharedPreferences { static const String _prefix = 'flutter.'; static Completer _completer; + + /// Loads and parses the [SharedPreferences] for this app from disk. + /// + /// Because this is reading from disk, it shouldn't be awaited in + /// performance-sensitive blocks. static Future getInstance() async { if (_completer == null) { _completer = Completer(); diff --git a/packages/shared_preferences/pubspec.yaml b/packages/shared_preferences/pubspec.yaml index dfce291036f0..89326d9a3aa9 100644 --- a/packages/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences -version: 0.5.4+5 +version: 0.5.4+6 flutter: plugin: From 4ace77817ebd457804b1462db5abfe6de193c48e Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Fri, 22 Nov 2019 14:39:46 -0800 Subject: [PATCH 31/47] [share] README update (#2297) Adds a little more info on how to use the plugin. Also adds linter options to prevent any undocumented APIs from being added in the future. --- packages/share/CHANGELOG.md | 4 ++++ packages/share/README.md | 14 ++++++++++++-- packages/share/analysis_options.yaml | 11 +++++++++++ packages/share/example/lib/main.dart | 2 ++ packages/share/pubspec.yaml | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 packages/share/analysis_options.yaml diff --git a/packages/share/CHANGELOG.md b/packages/share/CHANGELOG.md index 877cafefb2b9..f10814a7fb0a 100644 --- a/packages/share/CHANGELOG.md +++ b/packages/share/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.3+3 + +* README update. + ## 0.6.3+2 * Remove AndroidX warnings. diff --git a/packages/share/README.md b/packages/share/README.md index 4536c5818b7d..3a3aa06d9799 100644 --- a/packages/share/README.md +++ b/packages/share/README.md @@ -9,16 +9,26 @@ Wraps the ACTION_SEND Intent on Android and UIActivityViewController on iOS. ## Usage + To use this plugin, add `share` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). ## Example -Import the library via +Import the library. + ``` dart import 'package:share/share.dart'; ``` -Then invoke the static `share` method anywhere in your Dart code +Then invoke the static `share` method anywhere in your Dart code. + ``` dart Share.share('check out my website https://example.com'); ``` + +The `share` method also takes an optional `subject` that will be used when +sharing to email. + +``` dart +Share.share('check out my website https://example.com', subject: 'Look what I made!'); +``` diff --git a/packages/share/analysis_options.yaml b/packages/share/analysis_options.yaml new file mode 100644 index 000000000000..4d3c53a24cab --- /dev/null +++ b/packages/share/analysis_options.yaml @@ -0,0 +1,11 @@ +# This exists to add a lint for missing API docs just on this specific package, +# since not all packages have coverage for all their public members yet and +# adding it in would be non-trivial. `public_member_api_docs` should be applied +# to new packages going forward, and ideally the main `analysis_options.yaml` +# file as soon as possible. + +include: ../../analysis_options.yaml + +linter: + rules: + - public_member_api_docs diff --git a/packages/share/example/lib/main.dart b/packages/share/example/lib/main.dart index 1eb04e0f3c64..b68195cd3507 100644 --- a/packages/share/example/lib/main.dart +++ b/packages/share/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 'package:flutter/material.dart'; import 'package:share/share.dart'; diff --git a/packages/share/pubspec.yaml b/packages/share/pubspec.yaml index de376e8fb28b..0748bd2d9d8a 100644 --- a/packages/share/pubspec.yaml +++ b/packages/share/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for sharing content via the platform share UI, using the ACTION_SEND intent on Android and UIActivityViewController on iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/share -version: 0.6.3+2 +version: 0.6.3+3 flutter: plugin: From f31b9970757ad38f8ba2a24bd25e2a3b14d8dbd8 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Mon, 25 Nov 2019 14:17:23 -0800 Subject: [PATCH 32/47] [all] Switch to pedantic analysis rules (#2300) Switches our out of date fork of Flutter's analysis rules to the pedantic package. In order to make this an easy change to initially land and then incrementally enable, any existing non-trivial lints have just been ignored within their respective packages instead of being fixed. Also adds a global lint for missing public DartDocs. Like the pedantic lints, this is ignored in packages that are already failing it. --- analysis_options.yaml | 139 +----------------- .../analysis_options.yaml | 10 ++ packages/android_intent/analysis_options.yaml | 16 +- packages/battery/analysis_options.yaml | 11 ++ packages/camera/analysis_options.yaml | 11 ++ packages/connectivity/analysis_options.yaml | 15 +- packages/device_info/analysis_options.yaml | 11 ++ packages/e2e/analysis_options.yaml | 11 ++ .../example/lib/main.dart | 2 + .../google_maps_flutter/analysis_options.yaml | 11 ++ .../google_sign_in/analysis_options.yaml | 11 ++ .../analysis_options.yaml | 10 ++ .../google_sign_in_web/analysis_options.yaml | 15 +- packages/image_picker/analysis_options.yaml | 11 -- .../in_app_purchase/analysis_options.yaml | 17 ++- packages/local_auth/example/lib/main.dart | 3 + packages/local_auth/lib/auth_strings.dart | 6 + packages/local_auth/lib/local_auth.dart | 6 + packages/package_info/analysis_options.yaml | 11 ++ packages/path_provider/analysis_options.yaml | 11 -- .../test_driver/path_provider_e2e_test.dart | 2 +- packages/quick_actions/analysis_options.yaml | 11 ++ packages/sensors/analysis_options.yaml | 11 -- .../test_driver/test/sensors_e2e_test.dart | 2 +- packages/share/analysis_options.yaml | 15 +- .../shared_preferences/analysis_options.yaml | 16 +- .../url_launcher/analysis_options.yaml | 11 -- .../test_driver/url_launcher_e2e_test.dart | 2 +- .../analysis_options.yaml | 11 -- .../video_player/analysis_options.yaml | 15 +- .../analysis_options.yaml | 11 ++ .../webview_flutter/analysis_options.yaml | 11 ++ pubspec.yaml | 7 + 33 files changed, 225 insertions(+), 238 deletions(-) create mode 100644 packages/android_alarm_manager/analysis_options.yaml create mode 100644 packages/battery/analysis_options.yaml create mode 100644 packages/camera/analysis_options.yaml create mode 100644 packages/device_info/analysis_options.yaml create mode 100644 packages/e2e/analysis_options.yaml create mode 100644 packages/google_maps_flutter/analysis_options.yaml create mode 100644 packages/google_sign_in/google_sign_in/analysis_options.yaml create mode 100644 packages/google_sign_in/google_sign_in_platform_interface/analysis_options.yaml delete mode 100644 packages/image_picker/analysis_options.yaml create mode 100644 packages/package_info/analysis_options.yaml delete mode 100644 packages/path_provider/analysis_options.yaml create mode 100644 packages/quick_actions/analysis_options.yaml delete mode 100644 packages/sensors/analysis_options.yaml delete mode 100644 packages/url_launcher/url_launcher/analysis_options.yaml delete mode 100644 packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml create mode 100644 packages/video_player/video_player_platform_interface/analysis_options.yaml create mode 100644 packages/webview_flutter/analysis_options.yaml create mode 100644 pubspec.yaml diff --git a/analysis_options.yaml b/analysis_options.yaml index d4a06c5b9dd4..3a657c0e2408 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,138 +1,9 @@ -# Specify analysis options. -# -# Until there are meta linter rules, each desired lint must be explicitly enabled. -# See: https://github.com/dart-lang/linter/issues/288 -# -# For a list of lints, see: http://dart-lang.github.io/linter/lints/ -# See the configuration guide for more -# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer -# -# NOTE: Please keep this file in sync with -# https://github.com/flutter/flutter/blob/master/analysis_options.yaml - +include: package:pedantic/analysis_options.yaml analyzer: - language: - strict-inference: true - strict-raw-types: true - strong-mode: - implicit-dynamic: false - errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODOs in the code - todo: ignore exclude: - - 'bin/cache/**' - # the following two are relative to the stocks example and the flutter package respectively - # see https://github.com/dart-lang/sdk/issues/28463 - - 'lib/i18n/stock_messages_*.dart' - - 'lib/src/http/**' - + # Ignore generated files + - '**/*.g.dart' + - 'lib/src/generated/*.dart' linter: rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # http://dart-lang.github.io/linter/lints/ - - # === error rules === - - avoid_empty_else - - avoid_slow_async_io - - cancel_subscriptions - # - close_sinks # https://github.com/flutter/flutter/issues/5789 - # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153 - - control_flow_in_finally - - empty_statements - - hash_and_equals - # - invariant_booleans # https://github.com/flutter/flutter/issues/5790 - - iterable_contains_unrelated_type - - list_remove_unrelated_type - # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791 - - no_adjacent_strings_in_list - - no_duplicate_case_values - - test_types_in_equals - - throw_in_finally - - unrelated_type_equality_checks - - valid_regexps - - # === style rules === - - always_declare_return_types - # - always_put_control_body_on_new_line - - always_require_non_null_named_parameters - - always_specify_types - - annotate_overrides - # - avoid_annotating_with_dynamic # not yet tested - - avoid_as - # - avoid_catches_without_on_clauses # not yet tested - # - avoid_catching_errors # not yet tested - # - avoid_classes_with_only_static_members # not yet tested - # - avoid_function_literals_in_foreach_calls # not yet tested - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - - avoid_return_types_on_setters - # - avoid_returning_null # not yet tested - # - avoid_returning_this # not yet tested - # - avoid_setters_without_getters # not yet tested - # - avoid_types_on_closure_parameters # not yet tested - - await_only_futures - - camel_case_types - # - cascade_invocations # not yet tested - # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 - - directives_ordering - - empty_catches - - empty_constructor_bodies - - implementation_imports - # - join_return_with_assignment # not yet tested - - library_names - - library_prefixes - - non_constant_identifier_names - # - omit_local_variable_types # opposite of always_specify_types - # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 - - overridden_fields - - package_api_docs - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_collection_literals - # - prefer_conditional_assignment # not yet tested - - prefer_const_constructors - # - prefer_constructors_over_static_methods # not yet tested - - prefer_contains - - prefer_equal_for_default_values - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - # - prefer_final_fields # https://github.com/dart-lang/linter/issues/506 - - prefer_final_locals - # - prefer_foreach # not yet tested - # - prefer_function_declarations_over_variables # not yet tested - - prefer_initializing_formals - # - prefer_interpolation_to_compose_strings # not yet tested - - prefer_is_empty - - prefer_is_not_empty - - prefer_void_to_null - # - recursive_getters # https://github.com/dart-lang/linter/issues/452 - - slash_for_doc_comments - - sort_constructors_first - - sort_unnamed_constructors_first - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 - - unnecessary_brace_in_string_interps - - unnecessary_const - - unnecessary_getters_setters - # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498 - - unnecessary_new - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - # - unnecessary_overrides # https://github.com/dart-lang/linter/issues/626 and https://github.com/dart-lang/linter/issues/627 - - unnecessary_statements - - unnecessary_this - - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested - # - use_string_buffers # https://github.com/dart-lang/linter/pull/664 - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - # === pub rules === - - package_names + - public_member_api_docs diff --git a/packages/android_alarm_manager/analysis_options.yaml b/packages/android_alarm_manager/analysis_options.yaml new file mode 100644 index 000000000000..8e4af76f0a30 --- /dev/null +++ b/packages/android_alarm_manager/analysis_options.yaml @@ -0,0 +1,10 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore diff --git a/packages/android_intent/analysis_options.yaml b/packages/android_intent/analysis_options.yaml index 4d3c53a24cab..0eab8e531dc1 100644 --- a/packages/android_intent/analysis_options.yaml +++ b/packages/android_intent/analysis_options.yaml @@ -1,11 +1,11 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. include: ../../analysis_options.yaml -linter: - rules: - - public_member_api_docs +analyzer: + errors: + avoid_relative_lib_imports: ignore + unawaited_futures: ignore diff --git a/packages/battery/analysis_options.yaml b/packages/battery/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/battery/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/camera/analysis_options.yaml b/packages/camera/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/camera/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/connectivity/analysis_options.yaml b/packages/connectivity/analysis_options.yaml index 4d3c53a24cab..22c4e1041011 100644 --- a/packages/connectivity/analysis_options.yaml +++ b/packages/connectivity/analysis_options.yaml @@ -1,11 +1,10 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. include: ../../analysis_options.yaml -linter: - rules: - - public_member_api_docs +analyzer: + errors: + unawaited_futures: ignore diff --git a/packages/device_info/analysis_options.yaml b/packages/device_info/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/device_info/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/e2e/analysis_options.yaml b/packages/e2e/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/e2e/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/flutter_plugin_android_lifecycle/example/lib/main.dart b/packages/flutter_plugin_android_lifecycle/example/lib/main.dart index 12339516b156..01da8d66fc0d 100644 --- a/packages/flutter_plugin_android_lifecycle/example/lib/main.dart +++ b/packages/flutter_plugin_android_lifecycle/example/lib/main.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'package:flutter/material.dart'; void main() => runApp(MyApp()); diff --git a/packages/google_maps_flutter/analysis_options.yaml b/packages/google_maps_flutter/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/google_maps_flutter/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/google_sign_in/google_sign_in/analysis_options.yaml b/packages/google_sign_in/google_sign_in/analysis_options.yaml new file mode 100644 index 000000000000..e1dadb9e2b2a --- /dev/null +++ b/packages/google_sign_in/google_sign_in/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/google_sign_in/google_sign_in_platform_interface/analysis_options.yaml b/packages/google_sign_in/google_sign_in_platform_interface/analysis_options.yaml new file mode 100644 index 000000000000..969b55796944 --- /dev/null +++ b/packages/google_sign_in/google_sign_in_platform_interface/analysis_options.yaml @@ -0,0 +1,10 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore diff --git a/packages/google_sign_in/google_sign_in_web/analysis_options.yaml b/packages/google_sign_in/google_sign_in_web/analysis_options.yaml index 6ff44d022af6..af557b7ac2e0 100644 --- a/packages/google_sign_in/google_sign_in_web/analysis_options.yaml +++ b/packages/google_sign_in/google_sign_in_web/analysis_options.yaml @@ -1,5 +1,12 @@ -# Exclude auto-generated files from analysis/linting +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../../analysis_options.yaml + analyzer: - exclude: - - "lib/src/generated/**" - \ No newline at end of file + errors: + public_member_api_docs: ignore + unused_element: ignore + unawaited_futures: ignore diff --git a/packages/image_picker/analysis_options.yaml b/packages/image_picker/analysis_options.yaml deleted file mode 100644 index 4d3c53a24cab..000000000000 --- a/packages/image_picker/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. - -include: ../../analysis_options.yaml - -linter: - rules: - - public_member_api_docs diff --git a/packages/in_app_purchase/analysis_options.yaml b/packages/in_app_purchase/analysis_options.yaml index afa04c8cb084..6a095d6e3a69 100644 --- a/packages/in_app_purchase/analysis_options.yaml +++ b/packages/in_app_purchase/analysis_options.yaml @@ -1,3 +1,16 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + analyzer: - exclude: - - lib/**/*.g.dart # Ignore generated files \ No newline at end of file + errors: + avoid_init_to_null: ignore + prefer_is_empty: ignore + prefer_is_not_empty: ignore + public_member_api_docs: ignore + type_init_formals: ignore + unnecessary_new: ignore + unawaited_futures: ignore diff --git a/packages/local_auth/example/lib/main.dart b/packages/local_auth/example/lib/main.dart index 26dccb750289..06e33b9853be 100644 --- a/packages/local_auth/example/lib/main.dart +++ b/packages/local_auth/example/lib/main.dart @@ -1,6 +1,9 @@ // 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'; diff --git a/packages/local_auth/lib/auth_strings.dart b/packages/local_auth/lib/auth_strings.dart index 26646bbf41b4..a8f34f88723c 100644 --- a/packages/local_auth/lib/auth_strings.dart +++ b/packages/local_auth/lib/auth_strings.dart @@ -2,6 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This is a temporary ignore to allow us to land a new set of linter rules in a +// series of manageable patches instead of one gigantic PR. It disables some of +// the new lints that are already failing on this plugin, for this plugin. It +// should be deleted and the failing lints addressed as soon as possible. +// ignore_for_file: public_member_api_docs + import 'package:intl/intl.dart'; /// Android side authentication messages. diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index 31c1a41d0266..b2b03b920d64 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -2,6 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This is a temporary ignore to allow us to land a new set of linter rules in a +// series of manageable patches instead of one gigantic PR. It disables some of +// the new lints that are already failing on this plugin, for this plugin. It +// should be deleted and the failing lints addressed as soon as possible. +// ignore_for_file: public_member_api_docs + import 'dart:async'; import 'package:flutter/services.dart'; diff --git a/packages/package_info/analysis_options.yaml b/packages/package_info/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/package_info/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/path_provider/analysis_options.yaml b/packages/path_provider/analysis_options.yaml deleted file mode 100644 index 4d3c53a24cab..000000000000 --- a/packages/path_provider/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. - -include: ../../analysis_options.yaml - -linter: - rules: - - public_member_api_docs diff --git a/packages/path_provider/example/test_driver/path_provider_e2e_test.dart b/packages/path_provider/example/test_driver/path_provider_e2e_test.dart index ff6e9ce74ad9..f3aa9e218d82 100644 --- a/packages/path_provider/example/test_driver/path_provider_e2e_test.dart +++ b/packages/path_provider/example/test_driver/path_provider_e2e_test.dart @@ -10,6 +10,6 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); exit(result == 'pass' ? 0 : 1); } diff --git a/packages/quick_actions/analysis_options.yaml b/packages/quick_actions/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/quick_actions/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/sensors/analysis_options.yaml b/packages/sensors/analysis_options.yaml deleted file mode 100644 index 4d3c53a24cab..000000000000 --- a/packages/sensors/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. - -include: ../../analysis_options.yaml - -linter: - rules: - - public_member_api_docs diff --git a/packages/sensors/example/test_driver/test/sensors_e2e_test.dart b/packages/sensors/example/test_driver/test/sensors_e2e_test.dart index ff6e9ce74ad9..f3aa9e218d82 100644 --- a/packages/sensors/example/test_driver/test/sensors_e2e_test.dart +++ b/packages/sensors/example/test_driver/test/sensors_e2e_test.dart @@ -10,6 +10,6 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); exit(result == 'pass' ? 0 : 1); } diff --git a/packages/share/analysis_options.yaml b/packages/share/analysis_options.yaml index 4d3c53a24cab..22c4e1041011 100644 --- a/packages/share/analysis_options.yaml +++ b/packages/share/analysis_options.yaml @@ -1,11 +1,10 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. include: ../../analysis_options.yaml -linter: - rules: - - public_member_api_docs +analyzer: + errors: + unawaited_futures: ignore diff --git a/packages/shared_preferences/analysis_options.yaml b/packages/shared_preferences/analysis_options.yaml index 4d3c53a24cab..35344bfb898c 100644 --- a/packages/shared_preferences/analysis_options.yaml +++ b/packages/shared_preferences/analysis_options.yaml @@ -1,11 +1,11 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. include: ../../analysis_options.yaml -linter: - rules: - - public_member_api_docs +analyzer: + errors: + curly_braces_in_flow_control_structures: ignore + unawaited_futures: ignore diff --git a/packages/url_launcher/url_launcher/analysis_options.yaml b/packages/url_launcher/url_launcher/analysis_options.yaml deleted file mode 100644 index 6c7fd57bb022..000000000000 --- a/packages/url_launcher/url_launcher/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. - -include: ../../../analysis_options.yaml - -linter: - rules: - - public_member_api_docs diff --git a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart index ac4ea11482e2..1bcd0d37f450 100644 --- a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart +++ b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart @@ -10,6 +10,6 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); exit(result == 'pass' ? 0 : 1); } diff --git a/packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml b/packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml deleted file mode 100644 index 6c7fd57bb022..000000000000 --- a/packages/url_launcher/url_launcher_platform_interface/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. - -include: ../../../analysis_options.yaml - -linter: - rules: - - public_member_api_docs diff --git a/packages/video_player/video_player/analysis_options.yaml b/packages/video_player/video_player/analysis_options.yaml index 6c7fd57bb022..4c1fcb727314 100644 --- a/packages/video_player/video_player/analysis_options.yaml +++ b/packages/video_player/video_player/analysis_options.yaml @@ -1,11 +1,10 @@ -# This exists to add a lint for missing API docs just on this specific package, -# since not all packages have coverage for all their public members yet and -# adding it in would be non-trivial. `public_member_api_docs` should be applied -# to new packages going forward, and ideally the main `analysis_options.yaml` -# file as soon as possible. +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. include: ../../../analysis_options.yaml -linter: - rules: - - public_member_api_docs +analyzer: + errors: + unawaited_futures: ignore diff --git a/packages/video_player/video_player_platform_interface/analysis_options.yaml b/packages/video_player/video_player_platform_interface/analysis_options.yaml new file mode 100644 index 000000000000..e1dadb9e2b2a --- /dev/null +++ b/packages/video_player/video_player_platform_interface/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/packages/webview_flutter/analysis_options.yaml b/packages/webview_flutter/analysis_options.yaml new file mode 100644 index 000000000000..d4ccef63f1d1 --- /dev/null +++ b/packages/webview_flutter/analysis_options.yaml @@ -0,0 +1,11 @@ +# This is a temporary file to allow us to land a new set of linter rules in a +# series of manageable patches instead of one gigantic PR. It disables some of +# the new lints that are already failing on this plugin, for this plugin. It +# should be deleted and the failing lints addressed as soon as possible. + +include: ../../analysis_options.yaml + +analyzer: + errors: + public_member_api_docs: ignore + unawaited_futures: ignore diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 000000000000..23fb3db51bc0 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,7 @@ +# This only exists so that we can include pedantic in the top level +# analysis_options.yaml. Each plugin should be published from its own +# subdirectory underneath packages/. +name: flutter_plugins +dev_dependencies: + pedantic: ^1.8.0 +publish_to: none From 8eca03156bcba95b428ce0769dd09f4f08f5e81c Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Mon, 25 Nov 2019 15:26:38 -0800 Subject: [PATCH 33/47] [none] Add release process to CONTRIBUTING.md (#2304) This provides information on how to publish plugins going forward with the new `flutter_plugin_tools publish-plugin` command. In addition to that this documents our general process, which is a general team health issue we've been wanting to fix for awhile. --- CONTRIBUTING.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7edb13c08e60..f58dfea6a065 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -147,3 +147,74 @@ Newly opened PRs first go through initial triage which results in one of: * **Starting a non trivial review** - if the review requires non trivial effort and the issue is a priority; in this case the maintainer will: * Add the "in review" label to the issue. * Self assign the PR. + +### The release process + +We push releases manually. Generally every merged PR upgrades at least one +plugin's `pubspec.yaml`, so also needs to be published as a package release. The +Flutter team member most involved with the PR should be the person responsible +for publishing the package release. In cases where the PR is authored by a +Flutter maintainer, the publisher should probably be the author. In other cases +where the PR is from a contributor, it's up to the reviewing Flutter team member +to publish the release instead. + +Some things to keep in mind before publishing the release: + +- Has CI ran on the master commit and gone green? Even if CI shows as green on + the PR it's still possible for it to fail on merge, for multiple reasons. + There may have been some bug in the merge that introduced new failures. CI + runs on PRs as it's configured on their branch state, and not on tip of tree. + CI on PRs also only runs tests for packages that it detects have been directly + changed, vs running on every single package on master. +- [Publishing is + forever.](https://dart.dev/tools/pub/publishing#publishing-is-forever) + Hopefully any bugs or breaking in changes in this PR have already been caught + in PR review, but now's a second chance to revert before anything goes live. +- "Don't deploy on a Friday." Consider carefully whether or not it's worth + immediately publishing an update before a stretch of time where you're going + to be unavailable. There may be bugs with the release or questions about it + from people that immediately adopt it, and uncovering and resolving those + support issues will take more time if you're unavailable. + +Releasing a package is a two-step process. + +1. Push the package update to [pub.dev](https://pub.dev) using `pub publish`. +2. Tag the commit with git in the format of `-v`, + and then push the tag to the `flutter/plugins` master branch. This can be + done manually with `git tag $tagname && git push upstream $tagname` while + checked out on the commit that updated `version` in `pubspec.yaml`. + +We've recently updated +[flutter_plugin_tools](https://github.com/flutter/plugin_tools) to wrap both of +those steps into one command to make it a little easier. This new tool is +experimental. Feel free to fall back on manually running `pub publish` and +creating and pushing the tag in git if there are issues with it. + +Install the tool by running: + +```terminal +$ pub global activate flutter_plugin_tools +``` + +Then, from the root of your local `flutter/plugins` repo, use the tool to +publish a release. + +```terminal +$ pub global run flutter_plugin_tools publish-plugin --package $package +``` + +By default the tool tries to push tags to the `upstream` remote, but that and +some additional settings can be configured. Run `pub global activate +flutter_plugin_tools --help` for more usage information. + +The tool wraps `pub publish` for pushing the package to pub, and then will +automatically use git to try and create and push tags. It has some additional +safety checking around `pub publish` too. By default `pub publish` publishes +_everything_, including untracked or uncommitted files in version control. +`flutter_plugin_tools publish-plugin` will first check the status of the local +directory and refuse to publish if there are any mismatched files with version +control present. + +There is a lot about this process that is still to be desired. Some top level +items are being tracked in +[flutter/flutter#27258](https://github.com/flutter/flutter/issues/27258). From 2ea4bc8f8b5ae652f02e3db91b4b0adbdd499357 Mon Sep 17 00:00:00 2001 From: Yegor Date: Tue, 26 Nov 2019 06:46:35 -0800 Subject: [PATCH 34/47] add shared_preferences_platform_interface (#2291) * add shared_preferences_platform_interface --- .../shared_preferences/example/android.iml | 12 -- .../contents.xcworkspacedata | 10 -- .../contents.xcworkspacedata | 10 -- .../example/shared_preferences_example.iml | 15 --- .../{ => shared_preferences}/CHANGELOG.md | 4 + .../{ => shared_preferences}/LICENSE | 0 .../{ => shared_preferences}/README.md | 0 .../android/build.gradle | 0 .../android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../MethodCallHandlerImpl.java | 0 .../SharedPreferencesPlugin.java | 0 .../example/README.md | 0 .../example/android/app/build.gradle | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../EmbeddingV1Activity.java | 0 .../EmbeddingV1ActivityTest.java | 0 .../MainActivity.java | 0 .../MainActivityTest.java | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../example/android/build.gradle | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../example/android/settings.gradle | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../example/ios/Runner/AppDelegate.h | 0 .../example/ios/Runner/AppDelegate.m | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 0 .../example/ios/Runner/main.m | 0 .../example/lib/main.dart | 0 .../example/pubspec.yaml | 0 .../test_driver/shared_preferences_e2e.dart | 0 .../shared_preferences_e2e_test.dart | 0 .../ios/Assets/.gitkeep | 0 .../ios/Classes/SharedPreferencesPlugin.h | 0 .../ios/Classes/SharedPreferencesPlugin.m | 0 .../ios/shared_preferences.podspec | 0 .../lib/shared_preferences.dart | 0 .../{ => shared_preferences}/pubspec.yaml | 4 +- .../test/shared_preferences_test.dart | 0 .../CHANGELOG.md | 4 + .../LICENSE | 27 +++++ .../README.md | 25 ++++ .../method_channel_shared_preferences.dart | 56 +++++++++ ...shared_preferences_platform_interface.dart | 108 +++++++++++++++++ .../pubspec.yaml | 18 +++ ...ethod_channel_shared_preferences_test.dart | 112 ++++++++++++++++++ 75 files changed, 356 insertions(+), 49 deletions(-) delete mode 100644 packages/shared_preferences/example/android.iml delete mode 100644 packages/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/shared_preferences/example/ios/Runner.xcworkspace/contents.xcworkspacedata delete mode 100644 packages/shared_preferences/example/shared_preferences_example.iml rename packages/shared_preferences/{ => shared_preferences}/CHANGELOG.md (98%) rename packages/shared_preferences/{ => shared_preferences}/LICENSE (100%) rename packages/shared_preferences/{ => shared_preferences}/README.md (100%) rename packages/shared_preferences/{ => shared_preferences}/android/build.gradle (100%) rename packages/shared_preferences/{ => shared_preferences}/android/gradle.properties (100%) rename packages/shared_preferences/{ => shared_preferences}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/shared_preferences/{ => shared_preferences}/android/settings.gradle (100%) rename packages/shared_preferences/{ => shared_preferences}/android/src/main/AndroidManifest.xml (100%) rename packages/shared_preferences/{ => shared_preferences}/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java (100%) rename packages/shared_preferences/{ => shared_preferences}/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java (100%) rename packages/shared_preferences/{ => shared_preferences}/example/README.md (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/build.gradle (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/AndroidManifest.xml (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1Activity.java (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1ActivityTest.java (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivity.java (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/build.gradle (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/gradle.properties (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/shared_preferences/{ => shared_preferences}/example/android/settings.gradle (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Flutter/Debug.xcconfig (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Flutter/Release.xcconfig (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/AppDelegate.h (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/AppDelegate.m (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/Info.plist (100%) rename packages/shared_preferences/{ => shared_preferences}/example/ios/Runner/main.m (100%) rename packages/shared_preferences/{ => shared_preferences}/example/lib/main.dart (100%) rename packages/shared_preferences/{ => shared_preferences}/example/pubspec.yaml (100%) rename packages/shared_preferences/{ => shared_preferences}/example/test_driver/shared_preferences_e2e.dart (100%) rename packages/shared_preferences/{ => shared_preferences}/example/test_driver/shared_preferences_e2e_test.dart (100%) rename packages/shared_preferences/{ => shared_preferences}/ios/Assets/.gitkeep (100%) rename packages/shared_preferences/{ => shared_preferences}/ios/Classes/SharedPreferencesPlugin.h (100%) rename packages/shared_preferences/{ => shared_preferences}/ios/Classes/SharedPreferencesPlugin.m (100%) rename packages/shared_preferences/{ => shared_preferences}/ios/shared_preferences.podspec (100%) rename packages/shared_preferences/{ => shared_preferences}/lib/shared_preferences.dart (100%) rename packages/shared_preferences/{ => shared_preferences}/pubspec.yaml (92%) rename packages/shared_preferences/{ => shared_preferences}/test/shared_preferences_test.dart (100%) create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/LICENSE create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/README.md create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart diff --git a/packages/shared_preferences/example/android.iml b/packages/shared_preferences/example/android.iml deleted file mode 100644 index 462b903e05b6..000000000000 --- a/packages/shared_preferences/example/android.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c74e..000000000000 --- a/packages/shared_preferences/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/packages/shared_preferences/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/shared_preferences/example/ios/Runner.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 21a3cc14c74e..000000000000 --- a/packages/shared_preferences/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/packages/shared_preferences/example/shared_preferences_example.iml b/packages/shared_preferences/example/shared_preferences_example.iml deleted file mode 100644 index 9d5dae19540c..000000000000 --- a/packages/shared_preferences/example/shared_preferences_example.iml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md similarity index 98% rename from packages/shared_preferences/CHANGELOG.md rename to packages/shared_preferences/shared_preferences/CHANGELOG.md index 083d2b9b658a..15a50475078d 100644 --- a/packages/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.4+7 + +* Restructure the project for Web support. + ## 0.5.4+6 * Add missing documentation and a lint to prevent further undocumented APIs. diff --git a/packages/shared_preferences/LICENSE b/packages/shared_preferences/shared_preferences/LICENSE similarity index 100% rename from packages/shared_preferences/LICENSE rename to packages/shared_preferences/shared_preferences/LICENSE diff --git a/packages/shared_preferences/README.md b/packages/shared_preferences/shared_preferences/README.md similarity index 100% rename from packages/shared_preferences/README.md rename to packages/shared_preferences/shared_preferences/README.md diff --git a/packages/shared_preferences/android/build.gradle b/packages/shared_preferences/shared_preferences/android/build.gradle similarity index 100% rename from packages/shared_preferences/android/build.gradle rename to packages/shared_preferences/shared_preferences/android/build.gradle diff --git a/packages/shared_preferences/android/gradle.properties b/packages/shared_preferences/shared_preferences/android/gradle.properties similarity index 100% rename from packages/shared_preferences/android/gradle.properties rename to packages/shared_preferences/shared_preferences/android/gradle.properties diff --git a/packages/shared_preferences/android/gradle/wrapper/gradle-wrapper.properties b/packages/shared_preferences/shared_preferences/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/shared_preferences/android/gradle/wrapper/gradle-wrapper.properties rename to packages/shared_preferences/shared_preferences/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/shared_preferences/android/settings.gradle b/packages/shared_preferences/shared_preferences/android/settings.gradle similarity index 100% rename from packages/shared_preferences/android/settings.gradle rename to packages/shared_preferences/shared_preferences/android/settings.gradle diff --git a/packages/shared_preferences/android/src/main/AndroidManifest.xml b/packages/shared_preferences/shared_preferences/android/src/main/AndroidManifest.xml similarity index 100% rename from packages/shared_preferences/android/src/main/AndroidManifest.xml rename to packages/shared_preferences/shared_preferences/android/src/main/AndroidManifest.xml diff --git a/packages/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java b/packages/shared_preferences/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java similarity index 100% rename from packages/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java rename to packages/shared_preferences/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/MethodCallHandlerImpl.java diff --git a/packages/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java b/packages/shared_preferences/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java similarity index 100% rename from packages/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java rename to packages/shared_preferences/shared_preferences/android/src/main/java/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.java diff --git a/packages/shared_preferences/example/README.md b/packages/shared_preferences/shared_preferences/example/README.md similarity index 100% rename from packages/shared_preferences/example/README.md rename to packages/shared_preferences/shared_preferences/example/README.md diff --git a/packages/shared_preferences/example/android/app/build.gradle b/packages/shared_preferences/shared_preferences/example/android/app/build.gradle similarity index 100% rename from packages/shared_preferences/example/android/app/build.gradle rename to packages/shared_preferences/shared_preferences/example/android/app/build.gradle diff --git a/packages/shared_preferences/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/shared_preferences/shared_preferences/example/android/app/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/shared_preferences/example/android/app/gradle/wrapper/gradle-wrapper.properties rename to packages/shared_preferences/shared_preferences/example/android/app/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/shared_preferences/example/android/app/src/main/AndroidManifest.xml b/packages/shared_preferences/shared_preferences/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/AndroidManifest.xml rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1Activity.java b/packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1Activity.java similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1Activity.java rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1Activity.java diff --git a/packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1ActivityTest.java b/packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1ActivityTest.java similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1ActivityTest.java rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/EmbeddingV1ActivityTest.java diff --git a/packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivity.java b/packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivity.java similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivity.java rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivity.java diff --git a/packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java b/packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/java/io/flutter/plugins/sharedpreferencesexample/MainActivityTest.java diff --git a/packages/shared_preferences/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/shared_preferences/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/shared_preferences/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/shared_preferences/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/shared_preferences/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/shared_preferences/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/shared_preferences/shared_preferences/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/shared_preferences/example/android/build.gradle b/packages/shared_preferences/shared_preferences/example/android/build.gradle similarity index 100% rename from packages/shared_preferences/example/android/build.gradle rename to packages/shared_preferences/shared_preferences/example/android/build.gradle diff --git a/packages/shared_preferences/example/android/gradle.properties b/packages/shared_preferences/shared_preferences/example/android/gradle.properties similarity index 100% rename from packages/shared_preferences/example/android/gradle.properties rename to packages/shared_preferences/shared_preferences/example/android/gradle.properties diff --git a/packages/shared_preferences/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/shared_preferences/shared_preferences/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/shared_preferences/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/shared_preferences/shared_preferences/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/shared_preferences/example/android/settings.gradle b/packages/shared_preferences/shared_preferences/example/android/settings.gradle similarity index 100% rename from packages/shared_preferences/example/android/settings.gradle rename to packages/shared_preferences/shared_preferences/example/android/settings.gradle diff --git a/packages/shared_preferences/example/ios/Flutter/AppFrameworkInfo.plist b/packages/shared_preferences/shared_preferences/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/shared_preferences/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/shared_preferences/shared_preferences/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/shared_preferences/example/ios/Flutter/Debug.xcconfig b/packages/shared_preferences/shared_preferences/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/shared_preferences/example/ios/Flutter/Debug.xcconfig rename to packages/shared_preferences/shared_preferences/example/ios/Flutter/Debug.xcconfig diff --git a/packages/shared_preferences/example/ios/Flutter/Release.xcconfig b/packages/shared_preferences/shared_preferences/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/shared_preferences/example/ios/Flutter/Release.xcconfig rename to packages/shared_preferences/shared_preferences/example/ios/Flutter/Release.xcconfig diff --git a/packages/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj b/packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/shared_preferences/shared_preferences/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/shared_preferences/example/ios/Runner/AppDelegate.h b/packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.h similarity index 100% rename from packages/shared_preferences/example/ios/Runner/AppDelegate.h rename to packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.h diff --git a/packages/shared_preferences/example/ios/Runner/AppDelegate.m b/packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.m similarity index 100% rename from packages/shared_preferences/example/ios/Runner/AppDelegate.m rename to packages/shared_preferences/shared_preferences/example/ios/Runner/AppDelegate.m diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/shared_preferences/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/shared_preferences/shared_preferences/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/shared_preferences/example/ios/Runner/Base.lproj/Main.storyboard b/packages/shared_preferences/shared_preferences/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/shared_preferences/example/ios/Runner/Info.plist b/packages/shared_preferences/shared_preferences/example/ios/Runner/Info.plist similarity index 100% rename from packages/shared_preferences/example/ios/Runner/Info.plist rename to packages/shared_preferences/shared_preferences/example/ios/Runner/Info.plist diff --git a/packages/shared_preferences/example/ios/Runner/main.m b/packages/shared_preferences/shared_preferences/example/ios/Runner/main.m similarity index 100% rename from packages/shared_preferences/example/ios/Runner/main.m rename to packages/shared_preferences/shared_preferences/example/ios/Runner/main.m diff --git a/packages/shared_preferences/example/lib/main.dart b/packages/shared_preferences/shared_preferences/example/lib/main.dart similarity index 100% rename from packages/shared_preferences/example/lib/main.dart rename to packages/shared_preferences/shared_preferences/example/lib/main.dart diff --git a/packages/shared_preferences/example/pubspec.yaml b/packages/shared_preferences/shared_preferences/example/pubspec.yaml similarity index 100% rename from packages/shared_preferences/example/pubspec.yaml rename to packages/shared_preferences/shared_preferences/example/pubspec.yaml diff --git a/packages/shared_preferences/example/test_driver/shared_preferences_e2e.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart similarity index 100% rename from packages/shared_preferences/example/test_driver/shared_preferences_e2e.dart rename to packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart diff --git a/packages/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart similarity index 100% rename from packages/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart rename to packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart diff --git a/packages/shared_preferences/ios/Assets/.gitkeep b/packages/shared_preferences/shared_preferences/ios/Assets/.gitkeep similarity index 100% rename from packages/shared_preferences/ios/Assets/.gitkeep rename to packages/shared_preferences/shared_preferences/ios/Assets/.gitkeep diff --git a/packages/shared_preferences/ios/Classes/SharedPreferencesPlugin.h b/packages/shared_preferences/shared_preferences/ios/Classes/SharedPreferencesPlugin.h similarity index 100% rename from packages/shared_preferences/ios/Classes/SharedPreferencesPlugin.h rename to packages/shared_preferences/shared_preferences/ios/Classes/SharedPreferencesPlugin.h diff --git a/packages/shared_preferences/ios/Classes/SharedPreferencesPlugin.m b/packages/shared_preferences/shared_preferences/ios/Classes/SharedPreferencesPlugin.m similarity index 100% rename from packages/shared_preferences/ios/Classes/SharedPreferencesPlugin.m rename to packages/shared_preferences/shared_preferences/ios/Classes/SharedPreferencesPlugin.m diff --git a/packages/shared_preferences/ios/shared_preferences.podspec b/packages/shared_preferences/shared_preferences/ios/shared_preferences.podspec similarity index 100% rename from packages/shared_preferences/ios/shared_preferences.podspec rename to packages/shared_preferences/shared_preferences/ios/shared_preferences.podspec diff --git a/packages/shared_preferences/lib/shared_preferences.dart b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart similarity index 100% rename from packages/shared_preferences/lib/shared_preferences.dart rename to packages/shared_preferences/shared_preferences/lib/shared_preferences.dart diff --git a/packages/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml similarity index 92% rename from packages/shared_preferences/pubspec.yaml rename to packages/shared_preferences/shared_preferences/pubspec.yaml index 89326d9a3aa9..62853949d055 100644 --- a/packages/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -2,8 +2,8 @@ name: shared_preferences description: Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android. author: Flutter Team -homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences -version: 0.5.4+6 +homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences +version: 0.5.4+7 flutter: plugin: diff --git a/packages/shared_preferences/test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart similarity index 100% rename from packages/shared_preferences/test/shared_preferences_test.dart rename to packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart diff --git a/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..5797967fe365 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/CHANGELOG.md @@ -0,0 +1,4 @@ +## 1.0.0 + +* Initial release. Contains the interface and an implementation based on + method channels. diff --git a/packages/shared_preferences/shared_preferences_platform_interface/LICENSE b/packages/shared_preferences/shared_preferences_platform_interface/LICENSE new file mode 100644 index 000000000000..000b4618d2bd --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 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. \ No newline at end of file diff --git a/packages/shared_preferences/shared_preferences_platform_interface/README.md b/packages/shared_preferences/shared_preferences_platform_interface/README.md new file mode 100644 index 000000000000..301ba68ea361 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/README.md @@ -0,0 +1,25 @@ +# shared_preferences_platform_interface + +A common platform interface for the [`shared_preferences`][1] plugin. + +This interface allows platform-specific implementations of the `shared_preferences` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `shared_preferences`, extend +[`SharedPreferencesPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`SharedPreferencesLoader` by calling the `SharedPreferencesPlatform.loader` setter. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../shared_preferences +[2]: lib/shared_preferences_platform_interface.dart diff --git a/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart b/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart new file mode 100644 index 000000000000..66009a5caf14 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/lib/method_channel_shared_preferences.dart @@ -0,0 +1,56 @@ +// 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 'dart:async'; + +import 'package:flutter/services.dart'; + +import 'shared_preferences_platform_interface.dart'; + +const MethodChannel _kChannel = + MethodChannel('plugins.flutter.io/shared_preferences'); + +/// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing +/// a persistent store for simple data. +/// +/// Data is persisted to disk asynchronously. +class MethodChannelSharedPreferencesStore + extends SharedPreferencesStorePlatform { + @override + Future remove(String key) { + return _invokeBoolMethod('remove', { + 'key': key, + }); + } + + @override + Future setValue(String valueType, String key, Object value) { + return _invokeBoolMethod('set$valueType', { + 'key': key, + 'value': value, + }); + } + + Future _invokeBoolMethod(String method, Map params) { + return _kChannel + .invokeMethod(method, params) + // TODO(yjbanov): I copied this from the original + // shared_preferences.dart implementation, but I + // actually do not know why it's necessary to pipe the + // result through an identity function. + // + // Source: https://github.com/flutter/plugins/blob/3a87296a40a2624d200917d58f036baa9fb18df8/packages/shared_preferences/lib/shared_preferences.dart#L134 + .then((dynamic result) => result); + } + + @override + Future clear() { + return _kChannel.invokeMethod('clear'); + } + + @override + Future> getAll() { + return _kChannel.invokeMapMethod('getAll'); + } +} diff --git a/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart b/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart new file mode 100644 index 000000000000..ff42f6357e74 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart @@ -0,0 +1,108 @@ +// 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 'dart:async'; + +import 'package:meta/meta.dart'; + +import 'method_channel_shared_preferences.dart'; + +/// The interface that implementations of shared_preferences must implement. +/// +/// Platform implementations should extend this class rather than implement it as `shared_preferences` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [SharedPreferencesStorePlatform] methods. +abstract class SharedPreferencesStorePlatform { + /// The default instance of [SharedPreferencesStorePlatform] to use. + /// + /// Defaults to [MethodChannelSharedPreferencesStore]. + static SharedPreferencesStorePlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [SharedPreferencesStorePlatform] when they register themselves. + static set instance(SharedPreferencesStorePlatform value) { + try { + instance._verifyProvidesDefaultImplementations(); + _instance = value; + } on NoSuchMethodError catch (_) {} + } + + static SharedPreferencesStorePlatform _instance = + MethodChannelSharedPreferencesStore(); + + /// Only mock implementations should set this to true. + /// + /// Mockito mocks are implementing this class with `implements` which is forbidden for anything + /// other than mocks (see class docs). This property provides a backdoor for mockito mocks to + /// skip the verification that the class isn't implemented with `implements`. + @visibleForTesting + bool get isMock => false; + + /// Removes the value associated with the [key]. + Future remove(String key); + + /// Stores the [value] associated with the [key]. + /// + /// The [valueType] must match the type of [value] as follows: + /// + /// * Value type "Bool" must be passed if the value is of type `bool`. + /// * Value type "Double" must be passed if the value is of type `double`. + /// * Value type "Int" must be passed if the value is of type `int`. + /// * Value type "String" must be passed if the value is of type `String`. + /// * Value type "StringList" must be passed if the value is of type `List`. + Future setValue(String valueType, String key, Object value); + + /// Removes all keys and values in the store. + Future clear(); + + /// Returns all key/value pairs persisted in this store. + Future> getAll(); + + // This method makes sure that SharedPreferencesStorePlatform isn't implemented with `implements`. + // + // See class doc for more details on why implementing this class is forbidden. + // + // This private method is called by the instance setter, which fails if the class is + // implemented with `implements`. + void _verifyProvidesDefaultImplementations() {} +} + +/// Stores data in memory. +/// +/// Data does not persist across application restarts. This is useful in unit-tests. +class InMemorySharedPreferencesStore extends SharedPreferencesStorePlatform { + /// Instantiates an empty in-memory preferences store. + InMemorySharedPreferencesStore.empty() : _data = {}; + + /// Instantiates an in-memory preferences store containing a copy of [data]. + InMemorySharedPreferencesStore.withData(Map data) + : _data = Map.from(data); + + final Map _data; + + @override + Future clear() async { + _data.clear(); + return true; + } + + @override + Future> getAll() async { + return Map.from(_data); + } + + @override + Future remove(String key) async { + _data.remove(key); + return true; + } + + @override + Future setValue(String valueType, String key, Object value) async { + _data[key] = value; + return true; + } +} diff --git a/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..92efc896f777 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/pubspec.yaml @@ -0,0 +1,18 @@ +name: shared_preferences_platform_interface +description: A common platform interface for the shared_preferences plugin. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences_platform_interface +version: 1.0.0 + +dependencies: + meta: ^1.0.4 + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.6.7 <2.0.0" diff --git a/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart new file mode 100644 index 000000000000..4cc79b058675 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/test/method_channel_shared_preferences_test.dart @@ -0,0 +1,112 @@ +// 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 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; +import 'package:shared_preferences_platform_interface/method_channel_shared_preferences.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group(MethodChannelSharedPreferencesStore, () { + const MethodChannel channel = MethodChannel( + 'plugins.flutter.io/shared_preferences', + ); + + const Map kTestValues = { + 'flutter.String': 'hello world', + 'flutter.Bool': true, + 'flutter.Int': 42, + 'flutter.Double': 3.14159, + 'flutter.StringList': ['foo', 'bar'], + }; + + InMemorySharedPreferencesStore testData; + + final List log = []; + MethodChannelSharedPreferencesStore store; + + setUp(() async { + testData = InMemorySharedPreferencesStore.empty(); + + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + if (methodCall.method == 'getAll') { + return await testData.getAll(); + } + if (methodCall.method == 'remove') { + final String key = methodCall.arguments['key']; + return await testData.remove(key); + } + if (methodCall.method == 'clear') { + return await testData.clear(); + } + final RegExp setterRegExp = RegExp(r'set(.*)'); + final Match match = setterRegExp.matchAsPrefix(methodCall.method); + if (match.groupCount == 1) { + final String valueType = match.group(1); + final String key = methodCall.arguments['key']; + final Object value = methodCall.arguments['value']; + return await testData.setValue(valueType, key, value); + } + fail('Unexpected method call: ${methodCall.method}'); + }); + store = MethodChannelSharedPreferencesStore(); + log.clear(); + }); + + tearDown(() async { + await testData.clear(); + store = null; + testData = null; + }); + + test('getAll', () async { + testData = InMemorySharedPreferencesStore.withData(kTestValues); + expect(await store.getAll(), kTestValues); + expect(log.single.method, 'getAll'); + }); + + test('remove', () async { + testData = InMemorySharedPreferencesStore.withData(kTestValues); + expect(await store.remove('flutter.String'), true); + expect(await store.remove('flutter.Bool'), true); + expect(await store.remove('flutter.Int'), true); + expect(await store.remove('flutter.Double'), true); + expect(await testData.getAll(), { + 'flutter.StringList': ['foo', 'bar'], + }); + + expect(log, hasLength(4)); + for (MethodCall call in log) { + expect(call.method, 'remove'); + } + }); + + test('setValue', () async { + expect(await testData.getAll(), isEmpty); + for (String key in kTestValues.keys) { + final dynamic value = kTestValues[key]; + expect(await store.setValue(key.split('.').last, key, value), true); + } + expect(await testData.getAll(), kTestValues); + + expect(log, hasLength(5)); + expect(log[0].method, 'setString'); + expect(log[1].method, 'setBool'); + expect(log[2].method, 'setInt'); + expect(log[3].method, 'setDouble'); + expect(log[4].method, 'setStringList'); + }); + + test('clear', () async { + testData = InMemorySharedPreferencesStore.withData(kTestValues); + expect(await testData.getAll(), isNotEmpty); + expect(await store.clear(), true); + expect(await testData.getAll(), isEmpty); + expect(log.single.method, 'clear'); + }); + }); +} From 43f14ab0ec71474eecc388af7f74539c30e4c99c Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 26 Nov 2019 09:37:38 -0800 Subject: [PATCH 35/47] [quick_actions] Add DartDoc and test coverage (#2298) Also adds a lint to prevent undocumented DartDocs from being added in the future. Some of the APIs were public when they really should have been `@visibleForTesting` or even private. Added some `@visibleForTesting` annotations as well. --- packages/quick_actions/CHANGELOG.md | 11 ++++++++- packages/quick_actions/analysis_options.yaml | 11 --------- packages/quick_actions/example/lib/main.dart | 2 ++ .../test_driver/quick_actions_e2e_test.dart | 2 +- packages/quick_actions/lib/quick_actions.dart | 15 ++++++++---- packages/quick_actions/pubspec.yaml | 2 +- .../test/quick_actions_test.dart | 24 ++++++++++++++++--- 7 files changed, 46 insertions(+), 21 deletions(-) delete mode 100644 packages/quick_actions/analysis_options.yaml diff --git a/packages/quick_actions/CHANGELOG.md b/packages/quick_actions/CHANGELOG.md index ca7b674e77b5..2c9aea75ff0d 100644 --- a/packages/quick_actions/CHANGELOG.md +++ b/packages/quick_actions/CHANGELOG.md @@ -1,6 +1,15 @@ +## 0.4.0 + +- Added missing documentation. +- **Breaking change**. `channel` and `withMethodChannel` are now + `@visibleForTesting`. These methods are for plugin unit tests only and may be + removed in the future. +- **Breaking change**. Removed `runLaunchAction` from public API. This method + was not meant to be used by consumers of the plugin. + ## 0.3.3+1 -* Update and migrate iOS example project by removing flutter_assets, change +* Update and migrate iOS example project by removing flutter_assets, change "English" to "en", remove extraneous xcconfigs, update to Xcode 11 build settings, and remove ARCHS and DEVELOPMENT_TEAM. diff --git a/packages/quick_actions/analysis_options.yaml b/packages/quick_actions/analysis_options.yaml deleted file mode 100644 index d4ccef63f1d1..000000000000 --- a/packages/quick_actions/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - public_member_api_docs: ignore - unawaited_futures: ignore diff --git a/packages/quick_actions/example/lib/main.dart b/packages/quick_actions/example/lib/main.dart index dc4dc7a316fe..fc289810ea24 100644 --- a/packages/quick_actions/example/lib/main.dart +++ b/packages/quick_actions/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 'package:flutter/material.dart'; import 'package:quick_actions/quick_actions.dart'; diff --git a/packages/quick_actions/example/test_driver/quick_actions_e2e_test.dart b/packages/quick_actions/example/test_driver/quick_actions_e2e_test.dart index ff6e9ce74ad9..f3aa9e218d82 100644 --- a/packages/quick_actions/example/test_driver/quick_actions_e2e_test.dart +++ b/packages/quick_actions/example/test_driver/quick_actions_e2e_test.dart @@ -10,6 +10,6 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); exit(result == 'pass' ? 0 : 1); } diff --git a/packages/quick_actions/lib/quick_actions.dart b/packages/quick_actions/lib/quick_actions.dart index f240968eb8f5..933162a1a47c 100644 --- a/packages/quick_actions/lib/quick_actions.dart +++ b/packages/quick_actions/lib/quick_actions.dart @@ -17,6 +17,10 @@ typedef void QuickActionHandler(String type); /// Home screen quick-action shortcut item. class ShortcutItem { + /// Constructs an instance with the given [type], [localizedTitle], and + /// [icon]. + /// + /// Only [icon] should be nullable. It will remain `null` if unset. const ShortcutItem({ @required this.type, @required this.localizedTitle, @@ -36,14 +40,21 @@ class ShortcutItem { /// Quick actions plugin. class QuickActions { + /// Gets an instance of the plugin with the default methodChannel. + /// + /// [initialize] should be called before using any other methods. factory QuickActions() => _instance; + /// This is a test-only constructor. Do not call this, it can break at any + /// time. @visibleForTesting QuickActions.withMethodChannel(this.channel); static final QuickActions _instance = QuickActions.withMethodChannel(_kChannel); + /// This is a test-only accessor. Do not call this, it can break at any time. + @visibleForTesting final MethodChannel channel; /// Initializes this plugin. @@ -54,10 +65,6 @@ class QuickActions { assert(call.method == 'launch'); handler(call.arguments); }); - runLaunchAction(handler); - } - - void runLaunchAction(QuickActionHandler handler) async { final String action = await channel.invokeMethod('getLaunchAction'); if (action != null) { handler(action); diff --git a/packages/quick_actions/pubspec.yaml b/packages/quick_actions/pubspec.yaml index 5bb3467a63fc..ab8832865e5d 100644 --- a/packages/quick_actions/pubspec.yaml +++ b/packages/quick_actions/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for creating shortcuts on home screen, also known as Quick Actions on iOS and App Shortcuts on Android. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/quick_actions -version: 0.3.3+1 +version: 0.4.0 flutter: plugin: diff --git a/packages/quick_actions/test/quick_actions_test.dart b/packages/quick_actions/test/quick_actions_test.dart index 98a412e3e06d..ffb6de1024fd 100644 --- a/packages/quick_actions/test/quick_actions_test.dart +++ b/packages/quick_actions/test/quick_actions_test.dart @@ -1,6 +1,8 @@ // 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 'dart:async'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quick_actions/quick_actions.dart'; @@ -16,7 +18,7 @@ void main() { quickActions.channel.setMockMethodCallHandler( (MethodCall methodCall) async { log.add(methodCall); - return null; + return 'non empty response'; }, ); }); @@ -59,8 +61,9 @@ void main() { log.clear(); }); - test('runLaunchAction', () { - quickActions.runLaunchAction(null); + test('initialize', () async { + final Completer quickActionsHandler = Completer(); + quickActions.initialize((_) => quickActionsHandler.complete(true)); expect( log, [ @@ -68,5 +71,20 @@ void main() { ], ); log.clear(); + + expect(quickActionsHandler.future, completion(isTrue)); + }); + + test('Shortcut item can be constructed', () { + const String type = 'type'; + const String localizedTitle = 'title'; + const String icon = 'foo'; + + const ShortcutItem item = + ShortcutItem(type: type, localizedTitle: localizedTitle, icon: icon); + + expect(item.type, type); + expect(item.localizedTitle, localizedTitle); + expect(item.icon, icon); }); } From 1d1f457f12397820736ba3aa1a65d0a7950dd21b Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 26 Nov 2019 09:38:06 -0800 Subject: [PATCH 36/47] [android_alarm_manager] Testing and documentation (#2283) * [android_alarm_manager] Testing and documentation Unit tests the plugin, and adds a lint for missing DartDocs. * Review feedback - Wrap InstanceWrappers into a single `setTestOverride` method. - Replace potentially garbage collected lambdas with top level functions. * Review feedback --- packages/android_alarm_manager/CHANGELOG.md | 4 + .../analysis_options.yaml | 10 - .../example/lib/main.dart | 2 + .../lib/android_alarm_manager.dart | 34 ++- packages/android_alarm_manager/pubspec.yaml | 6 +- .../test/android_alarm_manager_test.dart | 201 ++++++++++++++++++ 6 files changed, 239 insertions(+), 18 deletions(-) delete mode 100644 packages/android_alarm_manager/analysis_options.yaml create mode 100644 packages/android_alarm_manager/test/android_alarm_manager_test.dart diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md index 1506fd304cf7..1494506fcb73 100644 --- a/packages/android_alarm_manager/CHANGELOG.md +++ b/packages/android_alarm_manager/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.4+3 + +* Add unit tests and DartDocs. + ## 0.4.4+2 * Remove AndroidX warning. diff --git a/packages/android_alarm_manager/analysis_options.yaml b/packages/android_alarm_manager/analysis_options.yaml deleted file mode 100644 index 8e4af76f0a30..000000000000 --- a/packages/android_alarm_manager/analysis_options.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - public_member_api_docs: ignore diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart index e68735a75085..5e20364312bb 100644 --- a/packages/android_alarm_manager/example/lib/main.dart +++ b/packages/android_alarm_manager/example/lib/main.dart @@ -2,6 +2,8 @@ // 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:android_alarm_manager/android_alarm_manager.dart'; diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index 76994b9edac7..f31b4bc87525 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -54,14 +54,34 @@ void _alarmManagerCallbackDispatcher() { _channel.invokeMethod('AlarmService.initialized'); } +// A lambda that returns the current instant in the form of a [DateTime]. +typedef DateTime _Now(); +// A lambda that gets the handle for the given [callback]. +typedef CallbackHandle _GetCallbackHandle(Function callback); + /// A Flutter plugin for registering Dart callbacks with the Android /// AlarmManager service. /// /// See the example/ directory in this package for sample usage. class AndroidAlarmManager { static const String _channelName = 'plugins.flutter.io/android_alarm_manager'; - static const MethodChannel _channel = - MethodChannel(_channelName, JSONMethodCodec()); + static MethodChannel _channel = + const MethodChannel(_channelName, JSONMethodCodec()); + // Function used to get the current time. It's [DateTime.now] by default. + static _Now _now = () => DateTime.now(); + // Callback used to get the handle for a callback. It's + // [PluginUtilities.getCallbackHandle] by default. + static _GetCallbackHandle _getCallbackHandle = + (Function callback) => PluginUtilities.getCallbackHandle(callback); + + /// This is exposed for the unit tests. It should not be accessed by users of + /// the plugin. + @visibleForTesting + static void setTestOverides( + {_Now now, _GetCallbackHandle getCallbackHandle}) { + _now = (now ?? _now); + _getCallbackHandle = (getCallbackHandle ?? _getCallbackHandle); + } /// Starts the [AndroidAlarmManager] service. This must be called before /// setting any alarms. @@ -70,7 +90,7 @@ class AndroidAlarmManager { /// failure. static Future initialize() async { final CallbackHandle handle = - PluginUtilities.getCallbackHandle(_alarmManagerCallbackDispatcher); + _getCallbackHandle(_alarmManagerCallbackDispatcher); if (handle == null) { return false; } @@ -127,7 +147,7 @@ class AndroidAlarmManager { bool rescheduleOnReboot = false, }) => oneShotAt( - DateTime.now().add(delay), + _now().add(delay), id, callback, alarmClock: alarmClock, @@ -188,7 +208,7 @@ class AndroidAlarmManager { assert(callback is Function() || callback is Function(int)); assert(id.bitLength < 32); final int startMillis = time.millisecondsSinceEpoch; - final CallbackHandle handle = PluginUtilities.getCallbackHandle(callback); + final CallbackHandle handle = _getCallbackHandle(callback); if (handle == null) { return false; } @@ -251,11 +271,11 @@ class AndroidAlarmManager { // ignore: inference_failure_on_function_return_type assert(callback is Function() || callback is Function(int)); assert(id.bitLength < 32); - final int now = DateTime.now().millisecondsSinceEpoch; + final int now = _now().millisecondsSinceEpoch; final int period = duration.inMilliseconds; final int first = startAt != null ? startAt.millisecondsSinceEpoch : now + period; - final CallbackHandle handle = PluginUtilities.getCallbackHandle(callback); + final CallbackHandle handle = _getCallbackHandle(callback); if (handle == null) { return false; } diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 6fe4ed944d55..29a9961b448d 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -1,7 +1,7 @@ name: android_alarm_manager description: Flutter plugin for accessing the Android AlarmManager service, and running Dart code in the background when alarms fire. -version: 0.4.4+2 +version: 0.4.4+3 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager @@ -9,6 +9,10 @@ dependencies: flutter: sdk: flutter +dev_dependencies: + flutter_test: + sdk: flutter + flutter: plugin: androidPackage: io.flutter.plugins.androidalarmmanager diff --git a/packages/android_alarm_manager/test/android_alarm_manager_test.dart b/packages/android_alarm_manager/test/android_alarm_manager_test.dart new file mode 100644 index 000000000000..1f9d2856838e --- /dev/null +++ b/packages/android_alarm_manager/test/android_alarm_manager_test.dart @@ -0,0 +1,201 @@ +// 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. + +import 'dart:ui'; + +import 'package:android_alarm_manager/android_alarm_manager.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + String invalidCallback(String foo) => foo; + void validCallback(int id) => null; + + const MethodChannel testChannel = MethodChannel( + 'plugins.flutter.io/android_alarm_manager', JSONMethodCodec()); + TestWidgetsFlutterBinding.ensureInitialized(); + + setUpAll(() { + testChannel.setMockMethodCallHandler((MethodCall call) => null); + }); + + test('${AndroidAlarmManager.initialize}', () async { + testChannel.setMockMethodCallHandler((MethodCall call) async { + assert(call.method == 'AlarmService.start'); + return true; + }); + + final bool initialized = await AndroidAlarmManager.initialize(); + + expect(initialized, isTrue); + }); + + group('${AndroidAlarmManager.oneShotAt}', () { + test('validates input', () async { + final DateTime validTime = DateTime.utc(1993); + final int validId = 1; + + // Callback should take a single int param. + await expectLater( + () => AndroidAlarmManager.oneShotAt( + validTime, validId, invalidCallback), + throwsAssertionError); + + // ID should be less than 32 bits. + await expectLater( + () => AndroidAlarmManager.oneShotAt( + validTime, 2147483648, validCallback), + throwsAssertionError); + }); + + test('sends arguments to the platform', () async { + final DateTime alarm = DateTime(1993); + const int rawHandle = 4; + AndroidAlarmManager.setTestOverides( + getCallbackHandle: (Function _) => + CallbackHandle.fromRawHandle(rawHandle)); + + final int id = 1; + final bool alarmClock = true; + final bool allowWhileIdle = true; + final bool exact = true; + final bool wakeup = true; + final bool rescheduleOnReboot = true; + + testChannel.setMockMethodCallHandler((MethodCall call) async { + expect(call.method, 'Alarm.oneShotAt'); + expect(call.arguments[0], id); + expect(call.arguments[1], alarmClock); + expect(call.arguments[2], allowWhileIdle); + expect(call.arguments[3], exact); + expect(call.arguments[4], wakeup); + expect(call.arguments[5], alarm.millisecondsSinceEpoch); + expect(call.arguments[6], rescheduleOnReboot); + expect(call.arguments[7], rawHandle); + return true; + }); + + final bool result = await AndroidAlarmManager.oneShotAt( + alarm, id, validCallback, + alarmClock: alarmClock, + allowWhileIdle: allowWhileIdle, + exact: exact, + wakeup: wakeup, + rescheduleOnReboot: rescheduleOnReboot); + + expect(result, isTrue); + }); + }); + + test('${AndroidAlarmManager.oneShot} calls through to oneShotAt', () async { + final DateTime now = DateTime(1993); + const int rawHandle = 4; + AndroidAlarmManager.setTestOverides( + now: () => now, + getCallbackHandle: (Function _) => + CallbackHandle.fromRawHandle(rawHandle)); + + const Duration alarm = Duration(seconds: 1); + final int id = 1; + final bool alarmClock = true; + final bool allowWhileIdle = true; + final bool exact = true; + final bool wakeup = true; + final bool rescheduleOnReboot = true; + + testChannel.setMockMethodCallHandler((MethodCall call) async { + expect(call.method, 'Alarm.oneShotAt'); + expect(call.arguments[0], id); + expect(call.arguments[1], alarmClock); + expect(call.arguments[2], allowWhileIdle); + expect(call.arguments[3], exact); + expect(call.arguments[4], wakeup); + expect( + call.arguments[5], now.millisecondsSinceEpoch + alarm.inMilliseconds); + expect(call.arguments[6], rescheduleOnReboot); + expect(call.arguments[7], rawHandle); + return true; + }); + + final bool result = await AndroidAlarmManager.oneShot( + alarm, id, validCallback, + alarmClock: alarmClock, + allowWhileIdle: allowWhileIdle, + exact: exact, + wakeup: wakeup, + rescheduleOnReboot: rescheduleOnReboot); + + expect(result, isTrue); + }); + + group('${AndroidAlarmManager.periodic}', () { + test('validates input', () async { + const Duration validDuration = Duration(seconds: 0); + final int validId = 1; + + // Callback should take a single int param. + await expectLater( + () => AndroidAlarmManager.periodic( + validDuration, validId, invalidCallback), + throwsAssertionError); + + // ID should be less than 32 bits. + await expectLater( + () => AndroidAlarmManager.periodic( + validDuration, 2147483648, validCallback), + throwsAssertionError); + }); + + test('sends arguments through to the platform', () async { + final DateTime now = DateTime(1993); + const int rawHandle = 4; + AndroidAlarmManager.setTestOverides( + now: () => now, + getCallbackHandle: (Function _) => + CallbackHandle.fromRawHandle(rawHandle)); + + final int id = 1; + final bool exact = true; + final bool wakeup = true; + final bool rescheduleOnReboot = true; + const Duration period = Duration(seconds: 1); + + testChannel.setMockMethodCallHandler((MethodCall call) async { + expect(call.method, 'Alarm.periodic'); + expect(call.arguments[0], id); + expect(call.arguments[1], exact); + expect(call.arguments[2], wakeup); + expect(call.arguments[3], + (now.millisecondsSinceEpoch + period.inMilliseconds)); + expect(call.arguments[4], period.inMilliseconds); + expect(call.arguments[5], rescheduleOnReboot); + expect(call.arguments[6], rawHandle); + return true; + }); + + final bool result = await AndroidAlarmManager.periodic( + period, + id, + (int id) => null, + exact: exact, + wakeup: wakeup, + rescheduleOnReboot: rescheduleOnReboot, + ); + + expect(result, isTrue); + }); + }); + + test('${AndroidAlarmManager.cancel}', () async { + final int id = 1; + testChannel.setMockMethodCallHandler((MethodCall call) async { + assert(call.method == 'Alarm.cancel' && call.arguments[0] == id); + return true; + }); + + final bool canceled = await AndroidAlarmManager.cancel(id); + + expect(canceled, isTrue); + }); +} From 2a01082353ba0997b8fde88caf76849c6c72244f Mon Sep 17 00:00:00 2001 From: Harry Terkelsen Date: Tue, 26 Nov 2019 09:57:58 -0800 Subject: [PATCH 37/47] Add Chrome to the Docker for CI (#2307) --- .ci/Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.ci/Dockerfile b/.ci/Dockerfile index bae957da08eb..13ac087498d1 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -22,3 +22,8 @@ RUN yes | sdkmanager \ "extras;android;m2repository" RUN yes | sdkmanager --licenses + +# Add repo for Google Chrome and install it +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 From f5df0a827ca9679bea986a171f9066af307c0911 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Tue, 26 Nov 2019 18:59:19 +0100 Subject: [PATCH 38/47] Emit correct VideoEventType (#2305) --- .../CHANGELOG.md | 4 + .../lib/method_channel_video_player.dart | 2 +- .../lib/video_player_platform_interface.dart | 30 +++++++ .../pubspec.yaml | 2 +- .../method_channel_video_player_test.dart | 85 ++++++++++++++++--- 5 files changed, 111 insertions(+), 12 deletions(-) diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 0d8803f93540..54d9e6ba4ecb 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +* Return correct platform event type when buffering + ## 1.0.0 * Initial release. diff --git a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart index 593c72684c1d..eb227ce18ec5 100644 --- a/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart +++ b/packages/video_player/video_player_platform_interface/lib/method_channel_video_player.dart @@ -139,7 +139,7 @@ class MethodChannelVideoPlayer extends VideoPlayerPlatform { return VideoEvent( buffered: values.map(_toDurationRange).toList(), - eventType: VideoEventType.completed, + eventType: VideoEventType.bufferingUpdate, ); case 'bufferingStart': return VideoEvent(eventType: VideoEventType.bufferingStart); diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 6c4ecaf58509..031baf6f218d 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart' show required, visibleForTesting; @@ -159,6 +160,24 @@ class VideoEvent { final Duration duration; final Size size; final List buffered; + + @override + bool operator ==(Object other) { + return identical(this, other) || + other is VideoEvent && + runtimeType == other.runtimeType && + eventType == other.eventType && + duration == other.duration && + size == other.size && + listEquals(buffered, other.buffered); + } + + @override + int get hashCode => + eventType.hashCode ^ + duration.hashCode ^ + size.hashCode ^ + buffered.hashCode; } enum VideoEventType { @@ -186,4 +205,15 @@ class DurationRange { @override String toString() => '$runtimeType(start: $start, end: $end)'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DurationRange && + runtimeType == other.runtimeType && + start == other.start && + end == other.end; + + @override + int get hashCode => start.hashCode ^ end.hashCode; } diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index bf4d4c03bce3..e36c9583e147 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.0 +version: 1.0.1 dependencies: flutter: diff --git a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart index 7db8ef6fbe0f..02c7bbba22b4 100644 --- a/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart +++ b/packages/video_player/video_player_platform_interface/test/method_channel_video_player_test.dart @@ -2,7 +2,6 @@ // 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:ui'; import 'package:mockito/mockito.dart'; @@ -245,6 +244,58 @@ void main() { }), (ByteData data) {}); + // TODO(cbenhagen): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.handlePlatformMessage( + "flutter.io/videoPlayer/videoEvents123", + const StandardMethodCodec() + .encodeSuccessEnvelope({ + 'event': 'completed', + }), + (ByteData data) {}); + + // TODO(cbenhagen): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.handlePlatformMessage( + "flutter.io/videoPlayer/videoEvents123", + const StandardMethodCodec() + .encodeSuccessEnvelope({ + 'event': 'bufferingUpdate', + 'values': >[ + [0, 1234], + [1235, 4000], + ], + }), + (ByteData data) {}); + + // TODO(cbenhagen): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.handlePlatformMessage( + "flutter.io/videoPlayer/videoEvents123", + const StandardMethodCodec() + .encodeSuccessEnvelope({ + 'event': 'bufferingStart', + }), + (ByteData data) {}); + + // TODO(cbenhagen): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.handlePlatformMessage( + "flutter.io/videoPlayer/videoEvents123", + const StandardMethodCodec() + .encodeSuccessEnvelope({ + 'event': 'bufferingEnd', + }), + (ByteData data) {}); + return const StandardMethodCodec().encodeSuccessEnvelope(null); } else if (methodCall.method == 'cancel') { return const StandardMethodCodec().encodeSuccessEnvelope(null); @@ -253,16 +304,30 @@ void main() { } }, ); - final Stream videoEvents = player.videoEventsFor(123); - expect((await videoEvents.first).eventType, VideoEventType.initialized); expect( - (await videoEvents.first).duration, - const Duration(milliseconds: 98765), - ); - expect( - (await videoEvents.first).size, - const Size(1920, 1080), - ); + player.videoEventsFor(123), + emitsInOrder([ + VideoEvent( + eventType: VideoEventType.initialized, + duration: const Duration(milliseconds: 98765), + size: const Size(1920, 1080), + ), + VideoEvent(eventType: VideoEventType.completed), + VideoEvent( + eventType: VideoEventType.bufferingUpdate, + buffered: [ + DurationRange( + const Duration(milliseconds: 0), + const Duration(milliseconds: 1234), + ), + DurationRange( + const Duration(milliseconds: 1235), + const Duration(milliseconds: 4000), + ), + ]), + VideoEvent(eventType: VideoEventType.bufferingStart), + VideoEvent(eventType: VideoEventType.bufferingEnd), + ])); }); }); } From 2d1509651c9138200fdbf86a46d51c498223a686 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 26 Nov 2019 14:12:39 -0800 Subject: [PATCH 39/47] [google_sign_in_web] Add empty podspec to plugin. (#2316) Add an empty podspec file, so this plugin doesn't break compilation on IOS. This should fix https://github.com/flutter/flutter/issues/45571 --- .../google_sign_in_web/CHANGELOG.md | 4 ++++ .../ios/google_sign_in_web.podspec | 21 +++++++++++++++++++ .../google_sign_in_web/pubspec.yaml | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/google_sign_in/google_sign_in_web/ios/google_sign_in_web.podspec diff --git a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md index f24bcf58874a..b2d801e77cbd 100644 --- a/packages/google_sign_in/google_sign_in_web/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_web/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.1 + +* Add podspec to enable compilation on iOS. + ## 0.8.0 * Flutter for web initial release \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_web/ios/google_sign_in_web.podspec b/packages/google_sign_in/google_sign_in_web/ios/google_sign_in_web.podspec new file mode 100644 index 000000000000..5e192172eb4b --- /dev/null +++ b/packages/google_sign_in/google_sign_in_web/ios/google_sign_in_web.podspec @@ -0,0 +1,21 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# +Pod::Spec.new do |s| + s.name = 'google_sign_in_web' + s.version = '0.8.1' + s.summary = 'No-op implementation of google_sign_in_web web plugin to avoid build issues on iOS' + s.description = <<-DESC + temp fake google_sign_in_web plugin + DESC + s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_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 + \ No newline at end of file diff --git a/packages/google_sign_in/google_sign_in_web/pubspec.yaml b/packages/google_sign_in/google_sign_in_web/pubspec.yaml index 0c33fe402e83..ebf20f09f921 100644 --- a/packages/google_sign_in/google_sign_in_web/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_web/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android, iOS and Web. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_web -version: 0.8.0 +version: 0.8.1 flutter: plugin: From 948744e9ee7c29026801f1cda86add1171eae8f0 Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 26 Nov 2019 14:49:35 -0800 Subject: [PATCH 40/47] [camera] Fix unawaited future errors (#2312) There's a over a dozen API surfaces that are undocumented still. This just fixes one of the new pedantic lints. --- packages/camera/CHANGELOG.md | 4 ++++ packages/camera/analysis_options.yaml | 1 - packages/camera/example/lib/main.dart | 2 ++ packages/camera/example/test_driver/camera_e2e.dart | 2 +- packages/camera/example/test_driver/camera_e2e_test.dart | 2 +- packages/camera/lib/camera.dart | 2 +- packages/camera/pubspec.yaml | 2 +- 7 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 8a3f248433eb..84c05e9c75c5 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.7 + +* Fix unawaited futures. + ## 0.5.6+4 * Android: Use CameraDevice.TEMPLATE_RECORD to improve image streaming. diff --git a/packages/camera/analysis_options.yaml b/packages/camera/analysis_options.yaml index d4ccef63f1d1..8e4af76f0a30 100644 --- a/packages/camera/analysis_options.yaml +++ b/packages/camera/analysis_options.yaml @@ -8,4 +8,3 @@ include: ../../analysis_options.yaml analyzer: errors: public_member_api_docs: ignore - unawaited_futures: ignore diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart index 1c4b11672530..9383a2098ad0 100644 --- a/packages/camera/example/lib/main.dart +++ b/packages/camera/example/lib/main.dart @@ -2,6 +2,8 @@ // 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 'dart:io'; diff --git a/packages/camera/example/test_driver/camera_e2e.dart b/packages/camera/example/test_driver/camera_e2e.dart index 151339942f15..a1cc8ad9ca02 100644 --- a/packages/camera/example/test_driver/camera_e2e.dart +++ b/packages/camera/example/test_driver/camera_e2e.dart @@ -231,7 +231,7 @@ void main() { sleep(const Duration(milliseconds: 500)); await controller.stopImageStream(); - controller.dispose(); + await controller.dispose(); }, skip: !Platform.isAndroid, ); diff --git a/packages/camera/example/test_driver/camera_e2e_test.dart b/packages/camera/example/test_driver/camera_e2e_test.dart index e3e089a81fc9..4963854dea72 100644 --- a/packages/camera/example/test_driver/camera_e2e_test.dart +++ b/packages/camera/example/test_driver/camera_e2e_test.dart @@ -35,7 +35,7 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); print('Test finished. Revoking camera permissions...'); Process.runSync('adb', [ 'shell', diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart index cc12b907fa65..ce9fd9430dde 100644 --- a/packages/camera/lib/camera.dart +++ b/packages/camera/lib/camera.dart @@ -445,7 +445,7 @@ class CameraController extends ValueNotifier { throw CameraException(e.code, e.message); } - _imageStreamSubscription.cancel(); + await _imageStreamSubscription.cancel(); _imageStreamSubscription = null; } diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index aae2d6e9112b..431e3344f6da 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.6+3 +version: 0.5.7 authors: - Flutter Team From 561da7fa206094ea23b9a093ffb343f5147805ef Mon Sep 17 00:00:00 2001 From: Michael Klimushyn Date: Tue, 26 Nov 2019 14:51:18 -0800 Subject: [PATCH 41/47] [android_intent] Fix pedantic linter errors (#2310) --- packages/android_intent/CHANGELOG.md | 4 ++++ packages/android_intent/analysis_options.yaml | 11 ----------- .../example/test_driver/android_intent_e2e.dart | 3 +-- .../example/test_driver/android_intent_e2e_test.dart | 2 +- packages/android_intent/pubspec.yaml | 2 +- packages/android_intent/test/android_intent_test.dart | 6 +++--- 6 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 packages/android_intent/analysis_options.yaml diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md index de7b818e0d0b..76db8d19fd8a 100644 --- a/packages/android_intent/CHANGELOG.md +++ b/packages/android_intent/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.4+7 + +* Fix pedantic linter errors. + ## 0.3.4+6 * Add missing DartDocs for public members. diff --git a/packages/android_intent/analysis_options.yaml b/packages/android_intent/analysis_options.yaml deleted file mode 100644 index 0eab8e531dc1..000000000000 --- a/packages/android_intent/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - avoid_relative_lib_imports: ignore - unawaited_futures: ignore diff --git a/packages/android_intent/example/test_driver/android_intent_e2e.dart b/packages/android_intent/example/test_driver/android_intent_e2e.dart index 8df8146f52be..2c45262147b1 100644 --- a/packages/android_intent/example/test_driver/android_intent_e2e.dart +++ b/packages/android_intent/example/test_driver/android_intent_e2e.dart @@ -1,13 +1,12 @@ import 'dart:io'; +import 'package:android_intent/android_intent.dart'; import 'package:android_intent_example/main.dart'; import 'package:e2e/e2e.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../../lib/android_intent.dart'; - /// This is a smoke test that verifies that the example app builds and loads. /// Because this plugin works by launching Android platform UIs it's not /// possible to meaningfully test it through its Dart interface currently. There diff --git a/packages/android_intent/example/test_driver/android_intent_e2e_test.dart b/packages/android_intent/example/test_driver/android_intent_e2e_test.dart index 4f38746ce76c..6147d44df2ec 100644 --- a/packages/android_intent/example/test_driver/android_intent_e2e_test.dart +++ b/packages/android_intent/example/test_driver/android_intent_e2e_test.dart @@ -7,6 +7,6 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); exit(result == 'pass' ? 0 : 1); } diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml index 3048d5bbbdf2..85b83b5a8f0a 100644 --- a/packages/android_intent/pubspec.yaml +++ b/packages/android_intent/pubspec.yaml @@ -2,7 +2,7 @@ name: android_intent description: Flutter plugin for launching Android Intents. Not supported on iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent -version: 0.3.4+6 +version: 0.3.4+7 flutter: plugin: diff --git a/packages/android_intent/test/android_intent_test.dart b/packages/android_intent/test/android_intent_test.dart index b13438bf7469..5562159d7bcc 100644 --- a/packages/android_intent/test/android_intent_test.dart +++ b/packages/android_intent/test/android_intent_test.dart @@ -23,7 +23,7 @@ void main() { flags: [Flag.FLAG_ACTIVITY_NEW_TASK], channel: mockChannel, platform: FakePlatform(operatingSystem: 'android')); - androidIntent.launch(); + await androidIntent.launch(); verify(mockChannel.invokeMethod('launch', { 'action': 'action_view', 'data': Uri.encodeFull('https://flutter.io'), @@ -35,7 +35,7 @@ void main() { action: null, channel: mockChannel, platform: FakePlatform(operatingSystem: 'android')); - androidIntent.launch(); + await androidIntent.launch(); verify(mockChannel.invokeMethod('launch', { 'action': null, })); @@ -46,7 +46,7 @@ void main() { action: null, channel: mockChannel, platform: FakePlatform(operatingSystem: 'ios')); - androidIntent.launch(); + await androidIntent.launch(); verifyZeroInteractions(mockChannel); }); }); From 330a3e0ddfe154fe99b49d670c347bb7e1009f54 Mon Sep 17 00:00:00 2001 From: David Iglesias Date: Tue, 26 Nov 2019 18:38:58 -0800 Subject: [PATCH 42/47] [script] Exclude google_sign_in_web from podspec lint. (#2318) Add google_sign_in_web to lint_darwin_plugins.sh so its dummy podspec isn't validated. --- script/lint_darwin_plugins.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/script/lint_darwin_plugins.sh b/script/lint_darwin_plugins.sh index 84c5f2a8dcfa..7e749707128c 100755 --- a/script/lint_darwin_plugins.sh +++ b/script/lint_darwin_plugins.sh @@ -16,6 +16,7 @@ function lint_package() { # These podspecs are temporary multi-platform adoption dummy files. local skipped_podspecs=( "url_launcher_web.podspec" + "google_sign_in_web.podspec" ) # TODO: These packages have analyzer warnings. Remove plugins from this list as issues are fixed. From bb604ef371a5781845a19a5e7173bd014ebe1e05 Mon Sep 17 00:00:00 2001 From: Mehmet Fidanboylu Date: Tue, 26 Nov 2019 19:59:50 -0800 Subject: [PATCH 43/47] [google_sign_in] Migrate away from quiver_hashcode (#2301) --- .../google_sign_in_platform_interface/CHANGELOG.md | 4 ++++ .../google_sign_in_platform_interface/lib/src/types.dart | 2 +- .../google_sign_in_platform_interface/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md index 0d8803f93540..343608d89d3e 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md +++ b/packages/google_sign_in/google_sign_in_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.1 + +* Switch away from quiver_hashcode. + ## 1.0.0 * Initial release. diff --git a/packages/google_sign_in/google_sign_in_platform_interface/lib/src/types.dart b/packages/google_sign_in/google_sign_in_platform_interface/lib/src/types.dart index 98b73c0667f2..44f0b30de5f8 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/lib/src/types.dart +++ b/packages/google_sign_in/google_sign_in_platform_interface/lib/src/types.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:quiver_hashcode/hashcode.dart'; +import 'package:quiver/core.dart'; enum SignInOption { standard, games } diff --git a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml index ceba7a1c516a..c791b3b7daa1 100644 --- a/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml +++ b/packages/google_sign_in/google_sign_in_platform_interface/pubspec.yaml @@ -4,13 +4,13 @@ author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in/google_sign_in_platform_interface # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 1.0.0 +version: 1.0.1 dependencies: flutter: sdk: flutter meta: ^1.0.5 - quiver_hashcode: ^2.0.0 + quiver: ">=2.0.0 <3.0.0" dev_dependencies: flutter_test: From b7b94b3571568acf0aea03ded2aecca3b28c97fd Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 27 Nov 2019 11:07:40 -0800 Subject: [PATCH 44/47] throw AssertionError on non-mock implementations (#2324) --- ...shared_preferences_platform_interface.dart | 13 +++-- ...d_preferences_platform_interface_test.dart | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart diff --git a/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart b/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart index ff42f6357e74..5a2b99ca69b1 100644 --- a/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart +++ b/packages/shared_preferences/shared_preferences_platform_interface/lib/shared_preferences_platform_interface.dart @@ -24,10 +24,15 @@ abstract class SharedPreferencesStorePlatform { /// Platform-specific plugins should set this with their own platform-specific /// class that extends [SharedPreferencesStorePlatform] when they register themselves. static set instance(SharedPreferencesStorePlatform value) { - try { - instance._verifyProvidesDefaultImplementations(); - _instance = value; - } on NoSuchMethodError catch (_) {} + if (!value.isMock) { + try { + value._verifyProvidesDefaultImplementations(); + } on NoSuchMethodError catch (_) { + throw AssertionError( + 'Platform interfaces must not be implemented with `implements`'); + } + } + _instance = value; } static SharedPreferencesStorePlatform _instance = diff --git a/packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart b/packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart new file mode 100644 index 000000000000..9e957734d174 --- /dev/null +++ b/packages/shared_preferences/shared_preferences_platform_interface/test/shared_preferences_platform_interface_test.dart @@ -0,0 +1,48 @@ +// 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 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group(SharedPreferencesStorePlatform, () { + test('disallows implementing interface', () { + expect( + () { + SharedPreferencesStorePlatform.instance = IllegalImplementation(); + }, + throwsAssertionError, + ); + }); + }); +} + +class IllegalImplementation implements SharedPreferencesStorePlatform { + // Intentionally declare self as not a mock to trigger the + // compliance check. + @override + bool get isMock => false; + + @override + Future clear() { + throw UnimplementedError(); + } + + @override + Future> getAll() { + throw UnimplementedError(); + } + + @override + Future remove(String key) { + throw UnimplementedError(); + } + + @override + Future setValue(String valueType, String key, Object value) { + throw UnimplementedError(); + } +} From e61e9d45bcaadc3e409d529d30735cb4db75c5c5 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Wed, 27 Nov 2019 13:42:13 -0800 Subject: [PATCH 45/47] [android_alarm_manager] migrate to the V2 Android embedding (#2193) --- packages/android_alarm_manager/CHANGELOG.md | 4 + .../android/build.gradle | 31 ++- .../androidalarmmanager/AlarmService.java | 198 ++++----------- .../AndroidAlarmManagerPlugin.java | 125 ++++------ .../FlutterBackgroundExecutor.java | 236 ++++++++++++++++++ .../androidalarmmanager/MainActivityTest.java | 15 ++ .../android/app/src/main/AndroidManifest.xml | 12 +- .../EmbeddingV1Activity.java | 19 ++ .../MainActivity.java | 25 +- .../example/android/gradle.properties | 1 + .../example/android/settings_aar.gradle | 1 + .../example/lib/main.dart | 4 +- .../example/pubspec.yaml | 5 + .../android_alarm_manager_e2e.dart | 101 ++++++++ .../android_alarm_manager_e2e_test.dart | 41 +++ .../lib/android_alarm_manager.dart | 9 +- packages/android_alarm_manager/pubspec.yaml | 10 +- 17 files changed, 591 insertions(+), 246 deletions(-) create mode 100644 packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java create mode 100644 packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java create mode 100644 packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java create mode 100644 packages/android_alarm_manager/example/android/settings_aar.gradle create mode 100644 packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart create mode 100644 packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md index 1494506fcb73..055086daf6d2 100644 --- a/packages/android_alarm_manager/CHANGELOG.md +++ b/packages/android_alarm_manager/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.5 + +* Add support for Flutter Android embedding V2 + ## 0.4.4+3 * Add unit tests and DartDocs. diff --git a/packages/android_alarm_manager/android/build.gradle b/packages/android_alarm_manager/android/build.gradle index d711772c2bb8..52da12479a40 100644 --- a/packages/android_alarm_manager/android/build.gradle +++ b/packages/android_alarm_manager/android/build.gradle @@ -23,7 +23,10 @@ apply plugin: 'com.android.library' android { compileSdkVersion 28 - + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -37,3 +40,29 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' api 'androidx.core:core:1.0.1' } + +// TODO(bkonyi): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + api 'android.arch.lifecycle:runtime:$lifecycle_version' + api 'android.arch.lifecycle:common:$lifecycle_version' + api 'android.arch.lifecycle:common-java8:$lifecycle_version' + } + } + } +} diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java index bb3d0c8db102..fb6e7f85b317 100644 --- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java @@ -13,12 +13,7 @@ import android.util.Log; import androidx.core.app.AlarmManagerCompat; import androidx.core.app.JobIntentService; -import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; -import io.flutter.view.FlutterCallbackInformation; -import io.flutter.view.FlutterMain; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterRunArguments; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -27,192 +22,88 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; import org.json.JSONException; import org.json.JSONObject; public class AlarmService extends JobIntentService { - // TODO(mattcarroll): tags should be private. Make private if no public usage. - public static final String TAG = "AlarmService"; - private static final String CALLBACK_HANDLE_KEY = "callback_handle"; + private static final String TAG = "AlarmService"; private static final String PERSISTENT_ALARMS_SET_KEY = "persistent_alarm_ids"; - private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin"; + protected static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin"; private static final int JOB_ID = 1984; // Random job ID. - private static final Object sPersistentAlarmsLock = new Object(); + private static final Object persistentAlarmsLock = new Object(); - // TODO(mattcarroll): make sIsIsolateRunning per-instance, not static. - private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false); - - // TODO(mattcarroll): make sAlarmQueue per-instance, not static. - private static List sAlarmQueue = Collections.synchronizedList(new LinkedList()); + // TODO(mattcarroll): make alarmQueue per-instance, not static. + private static List alarmQueue = Collections.synchronizedList(new LinkedList()); /** Background Dart execution context. */ - private static FlutterNativeView sBackgroundFlutterView; - - /** - * The {@link MethodChannel} that connects the Android side of this plugin with the background - * Dart isolate that was created by this plugin. - */ - private static MethodChannel sBackgroundChannel; - - private static PluginRegistrantCallback sPluginRegistrantCallback; + private static FlutterBackgroundExecutor flutterBackgroundExecutor; - // Schedule the alarm to be handled by the AlarmService. + /** Schedule the alarm to be handled by the {@link AlarmService}. */ public static void enqueueAlarmProcessing(Context context, Intent alarmContext) { enqueueWork(context, AlarmService.class, JOB_ID, alarmContext); } /** - * Starts running a background Dart isolate within a new {@link FlutterNativeView}. - * - *

The isolate is configured as follows: - * - *

    - *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. - *
  • Entrypoint: The Dart method represented by {@code callbackHandle}. - *
  • Run args: none. - *
+ * Starts the background isolate for the {@link AlarmService}. * *

Preconditions: * *

    *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the * handle does not resolve to a Dart callback then this method does nothing. - *
  • A static {@link #sPluginRegistrantCallback} must exist, otherwise a {@link + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link * PluginRegistrantException} will be thrown. *
*/ public static void startBackgroundIsolate(Context context, long callbackHandle) { - // TODO(mattcarroll): re-arrange order of operations. The order is strange - there are 3 - // conditions that must be met for this method to do anything but they're split up for no - // apparent reason. Do the qualification checks first, then execute the method's logic. - FlutterMain.ensureInitializationComplete(context, null); - String mAppBundlePath = FlutterMain.findAppBundlePath(context); - FlutterCallbackInformation flutterCallback = - FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); - if (flutterCallback == null) { - Log.e(TAG, "Fatal: failed to find callback"); + if (flutterBackgroundExecutor != null) { + Log.w(TAG, "Attempted to start a duplicate background isolate. Returning..."); return; } - - // Note that we're passing `true` as the second argument to our - // FlutterNativeView constructor. This specifies the FlutterNativeView - // as a background view and does not create a drawing surface. - sBackgroundFlutterView = new FlutterNativeView(context, true); - if (mAppBundlePath != null && !sIsIsolateRunning.get()) { - if (sPluginRegistrantCallback == null) { - throw new PluginRegistrantException(); - } - Log.i(TAG, "Starting AlarmService..."); - FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = mAppBundlePath; - args.entrypoint = flutterCallback.callbackName; - args.libraryPath = flutterCallback.callbackLibraryPath; - sBackgroundFlutterView.runFromBundle(args); - sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); - } + flutterBackgroundExecutor = new FlutterBackgroundExecutor(); + flutterBackgroundExecutor.startBackgroundIsolate(context, callbackHandle); } /** - * Called once the Dart isolate ({@code sBackgroundFlutterView}) has finished initializing. + * Called once the Dart isolate ({@code flutterBackgroundExecutor}) has finished initializing. * *

Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code * AlarmService.initialized} message. Processes all alarm events that came in while the isolate * was starting. */ - // TODO(mattcarroll): consider making this method package private - public static void onInitialized() { + /* package */ static void onInitialized() { Log.i(TAG, "AlarmService started!"); - sIsIsolateRunning.set(true); - synchronized (sAlarmQueue) { + synchronized (alarmQueue) { // Handle all the alarm events received before the Dart isolate was // initialized, then clear the queue. - Iterator i = sAlarmQueue.iterator(); + Iterator i = alarmQueue.iterator(); while (i.hasNext()) { - executeDartCallbackInBackgroundIsolate(i.next(), null); + flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(i.next(), null); } - sAlarmQueue.clear(); + alarmQueue.clear(); } } - /** - * Sets the {@link MethodChannel} that is used to communicate with Dart callbacks that are invoked - * in the background by the android_alarm_manager plugin. - */ - public static void setBackgroundChannel(MethodChannel channel) { - sBackgroundChannel = channel; - } - /** * Sets the Dart callback handle for the Dart method that is responsible for initializing the * background Dart isolate, preparing it to receive Dart callback tasks requests. */ public static void setCallbackDispatcher(Context context, long callbackHandle) { - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); - } - - public static boolean setBackgroundFlutterView(FlutterNativeView view) { - if (sBackgroundFlutterView != null && sBackgroundFlutterView != view) { - Log.i(TAG, "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView"); - return false; - } - sBackgroundFlutterView = view; - return true; - } - - public static void setPluginRegistrant(PluginRegistrantCallback callback) { - sPluginRegistrantCallback = callback; + FlutterBackgroundExecutor.setCallbackDispatcher(context, callbackHandle); } /** - * Executes the desired Dart callback in a background Dart isolate. + * Sets the {@link PluginRegistrantCallback} used to register the plugins used by an application + * with the newly spawned background isolate. * - *

The given {@code intent} should contain a {@code long} extra called "callbackHandle", which - * corresponds to a callback registered with the Dart VM. + *

This should be invoked in {@link Application.onCreate} with {@link + * GeneratedPluginRegistrant} in applications using the V1 embedding API in order to use other + * plugins in the background isolate. For applications using the V2 embedding API, it is not + * necessary to set a {@link PluginRegistrantCallback} as plugins are registered automatically. */ - private static void executeDartCallbackInBackgroundIsolate( - Intent intent, final CountDownLatch latch) { - // Grab the handle for the callback associated with this alarm. Pay close - // attention to the type of the callback handle as storing this value in a - // variable of the wrong size will cause the callback lookup to fail. - long callbackHandle = intent.getLongExtra("callbackHandle", 0); - if (sBackgroundChannel == null) { - Log.e( - TAG, - "setBackgroundChannel was not called before alarms were scheduled." + " Bailing out."); - return; - } - - // If another thread is waiting, then wake that thread when the callback returns a result. - MethodChannel.Result result = null; - if (latch != null) { - result = - new MethodChannel.Result() { - @Override - public void success(Object result) { - latch.countDown(); - } - - @Override - public void error(String errorCode, String errorMessage, Object errorDetails) { - latch.countDown(); - } - - @Override - public void notImplemented() { - latch.countDown(); - } - }; - } - - // Handle the alarm event in Dart. Note that for this plugin, we don't - // care about the method name as we simply lookup and invoke the callback - // provided. - // TODO(mattcarroll): consider giving a method name anyway for the purpose of developer discoverability - // when reading the source code. Especially on the Dart side. - sBackgroundChannel.invokeMethod( - "", new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, result); + public static void setPluginRegistrant(PluginRegistrantCallback callback) { + // Indirectly set in FlutterBackgroundExecutor for backwards compatibility. + FlutterBackgroundExecutor.setPluginRegistrant(callback); } private static void scheduleAlarm( @@ -285,6 +176,7 @@ private static void scheduleAlarm( } } + /** Schedules a one-shot alarm to be executed once in the future. */ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShotRequest request) { final boolean repeating = false; scheduleAlarm( @@ -301,6 +193,7 @@ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShot request.callbackHandle); } + /** Schedules a periodic alarm to be executed repeatedly in the future. */ public static void setPeriodic( Context context, AndroidAlarmManagerPlugin.PeriodicRequest request) { final boolean repeating = true; @@ -320,6 +213,7 @@ public static void setPeriodic( request.callbackHandle); } + /** Cancels an alarm with ID {@code requestCode}. */ public static void cancel(Context context, int requestCode) { // Clear the alarm if it was set to be rescheduled after reboots. clearPersistentAlarm(context, requestCode); @@ -364,7 +258,7 @@ private static void addPersistentAlarm( String key = getPersistentAlarmKey(requestCode); SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - synchronized (sPersistentAlarmsLock) { + synchronized (persistentAlarmsLock) { Set persistentAlarms = prefs.getStringSet(PERSISTENT_ALARMS_SET_KEY, null); if (persistentAlarms == null) { persistentAlarms = new HashSet<>(); @@ -383,7 +277,7 @@ private static void addPersistentAlarm( private static void clearPersistentAlarm(Context context, int requestCode) { SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - synchronized (sPersistentAlarmsLock) { + synchronized (persistentAlarmsLock) { Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null); if ((persistentAlarms == null) || !persistentAlarms.contains(requestCode)) { return; @@ -399,7 +293,7 @@ private static void clearPersistentAlarm(Context context, int requestCode) { } public static void reschedulePersistentAlarms(Context context) { - synchronized (sPersistentAlarmsLock) { + synchronized (persistentAlarmsLock) { SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null); // No alarms to reschedule. @@ -449,15 +343,11 @@ public static void reschedulePersistentAlarms(Context context) { @Override public void onCreate() { super.onCreate(); - - Context context = getApplicationContext(); - FlutterMain.ensureInitializationComplete(context, null); - - if (!sIsIsolateRunning.get()) { - SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - long callbackHandle = p.getLong(CALLBACK_HANDLE_KEY, 0); - startBackgroundIsolate(context, callbackHandle); + if (flutterBackgroundExecutor == null) { + flutterBackgroundExecutor = new FlutterBackgroundExecutor(); } + Context context = getApplicationContext(); + flutterBackgroundExecutor.startBackgroundIsolate(context); } /** @@ -470,17 +360,17 @@ public void onCreate() { * intent}, then the desired Dart callback is invoked immediately. * *

If there are any pre-existing callback requests that have yet to be executed, the incoming - * {@code intent} is added to the {@link #sAlarmQueue} to invoked later, after all pre-existing + * {@code intent} is added to the {@link #alarmQueue} to invoked later, after all pre-existing * callbacks have been executed. */ @Override protected void onHandleWork(final Intent intent) { // If we're in the middle of processing queued alarms, add the incoming // intent to the queue and return. - synchronized (sAlarmQueue) { - if (!sIsIsolateRunning.get()) { + synchronized (alarmQueue) { + if (!flutterBackgroundExecutor.isRunning()) { Log.i(TAG, "AlarmService has not yet started."); - sAlarmQueue.add(intent); + alarmQueue.add(intent); return; } } @@ -493,7 +383,7 @@ protected void onHandleWork(final Intent intent) { new Runnable() { @Override public void run() { - executeDartCallbackInBackgroundIsolate(intent, latch); + flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(intent, latch); } }); diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java index 5cc77413928e..9db627022f72 100644 --- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java @@ -5,13 +5,15 @@ package io.flutter.plugins.androidalarmmanager; import android.content.Context; +import android.util.Log; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; -import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener; import io.flutter.view.FlutterNativeView; import org.json.JSONArray; import org.json.JSONException; @@ -38,7 +40,13 @@ * Dart is ready to execute tasks. * */ -public class AndroidAlarmManagerPlugin implements MethodCallHandler, ViewDestroyListener { +public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandler { + private static AndroidAlarmManagerPlugin instance; + private final String TAG = "AndroidAlarmManagerPlugin"; + private Context context; + private Object initializationLock = new Object(); + private MethodChannel alarmManagerPluginChannel; + /** * Registers this plugin with an associated Flutter execution context, represented by the given * {@link Registrar}. @@ -47,54 +55,53 @@ public class AndroidAlarmManagerPlugin implements MethodCallHandler, ViewDestroy * connected to, and running against, the associated Flutter execution context. */ public static void registerWith(Registrar registrar) { - // alarmManagerPluginChannel is the channel responsible for receiving the following messages - // from the main Flutter app: - // - "AlarmService.start" - // - "Alarm.oneShotAt" - // - "Alarm.periodic" - // - "Alarm.cancel" - final MethodChannel alarmManagerPluginChannel = - new MethodChannel( - registrar.messenger(), - "plugins.flutter.io/android_alarm_manager", - JSONMethodCodec.INSTANCE); + if (instance == null) { + instance = new AndroidAlarmManagerPlugin(); + } + instance.onAttachedToEngine(registrar.context(), registrar.messenger()); + } - // backgroundCallbackChannel is the channel responsible for receiving the following messages - // from the background isolate that was setup by this plugin: - // - "AlarmService.initialized" - // - // This channel is also responsible for sending requests from Android to Dart to execute Dart - // callbacks in the background isolate. Those messages are sent with an empty method name because - // they are the only messages that this channel sends to Dart. - final MethodChannel backgroundCallbackChannel = - new MethodChannel( - registrar.messenger(), - "plugins.flutter.io/android_alarm_manager_background", - JSONMethodCodec.INSTANCE); + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + onAttachedToEngine( + binding.getApplicationContext(), binding.getFlutterEngine().getDartExecutor()); + } - // Instantiate a new AndroidAlarmManagerPlugin, connect the primary and background - // method channels for Android/Flutter communication, and listen for FlutterView - // destruction so that this plugin can move itself to background mode. - AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(registrar.context()); - alarmManagerPluginChannel.setMethodCallHandler(plugin); - backgroundCallbackChannel.setMethodCallHandler(plugin); - registrar.addViewDestroyListener(plugin); + public void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { + synchronized (initializationLock) { + if (alarmManagerPluginChannel != null) { + return; + } - // The AlarmService expects to hold a static reference to the plugin's background - // method channel. - // TODO(mattcarroll): this static reference implies that only one instance of this plugin - // can exist at a time. Moreover, calling registerWith() a 2nd time would - // seem to overwrite the previously registered background channel without - // notice. - AlarmService.setBackgroundChannel(backgroundCallbackChannel); + Log.i(TAG, "onAttachedToEngine"); + this.context = applicationContext; + + // alarmManagerPluginChannel is the channel responsible for receiving the following messages + // from the main Flutter app: + // - "AlarmService.start" + // - "Alarm.oneShotAt" + // - "Alarm.periodic" + // - "Alarm.cancel" + alarmManagerPluginChannel = + new MethodChannel( + messenger, "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); + + // Instantiate a new AndroidAlarmManagerPlugin and connect the primary method channel for + // Android/Flutter communication. + alarmManagerPluginChannel.setMethodCallHandler(this); + } } - private Context mContext; - - private AndroidAlarmManagerPlugin(Context context) { - this.mContext = context; + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + Log.i(TAG, "onDetachedFromEngine"); + context = null; + alarmManagerPluginChannel.setMethodCallHandler(null); + alarmManagerPluginChannel = null; } + public AndroidAlarmManagerPlugin() {} + /** Invoked when the Flutter side of this plugin sends a message to the Android side. */ @Override public void onMethodCall(MethodCall call, Result result) { @@ -109,33 +116,26 @@ public void onMethodCall(MethodCall call, Result result) { // method channel to communicate with the new background isolate. Once completed, // this onMethodCall() method will receive messages from both the primary and background // method channels. - AlarmService.setCallbackDispatcher(mContext, callbackHandle); - AlarmService.startBackgroundIsolate(mContext, callbackHandle); - result.success(true); - } else if (method.equals("AlarmService.initialized")) { - // This message is sent by the background method channel as soon as the background isolate - // is running. From this point forward, the Android side of this plugin can send - // callback handles through the background method channel, and the Dart side will execute - // the Dart methods corresponding to those callback handles. - AlarmService.onInitialized(); + AlarmService.setCallbackDispatcher(context, callbackHandle); + AlarmService.startBackgroundIsolate(context, callbackHandle); result.success(true); } else if (method.equals("Alarm.periodic")) { // This message indicates that the Flutter app would like to schedule a periodic // task. PeriodicRequest periodicRequest = PeriodicRequest.fromJson((JSONArray) arguments); - AlarmService.setPeriodic(mContext, periodicRequest); + AlarmService.setPeriodic(context, periodicRequest); result.success(true); } else if (method.equals("Alarm.oneShotAt")) { // This message indicates that the Flutter app would like to schedule a one-time // task. OneShotRequest oneShotRequest = OneShotRequest.fromJson((JSONArray) arguments); - AlarmService.setOneShot(mContext, oneShotRequest); + AlarmService.setOneShot(context, oneShotRequest); result.success(true); } else if (method.equals("Alarm.cancel")) { // This message indicates that the Flutter app would like to cancel a previously // scheduled task. int requestCode = ((JSONArray) arguments).getInt(0); - AlarmService.cancel(mContext, requestCode); + AlarmService.cancel(context, requestCode); result.success(true); } else { result.notImplemented(); @@ -147,21 +147,6 @@ public void onMethodCall(MethodCall call, Result result) { } } - /** - * Transitions the Flutter execution context that owns this plugin from foreground execution to - * background execution. - * - *

Invoked when the {@link FlutterView} connected to the given {@link FlutterNativeView} is - * destroyed. - * - *

Returns true if the given {@code nativeView} was successfully stored by this plugin, or - * false if a different {@link FlutterNativeView} was already registered with this plugin. - */ - @Override - public boolean onViewDestroy(FlutterNativeView nativeView) { - return AlarmService.setBackgroundFlutterView(nativeView); - } - /** A request to schedule a one-shot Dart task. */ static final class OneShotRequest { static OneShotRequest fromJson(JSONArray json) throws JSONException { diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java new file mode 100644 index 000000000000..c7eebc2332bf --- /dev/null +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java @@ -0,0 +1,236 @@ +// 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. + +package io.flutter.plugins.androidalarmmanager; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.util.Log; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartCallback; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; +import io.flutter.view.FlutterCallbackInformation; +import io.flutter.view.FlutterMain; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An background execution abstraction which handles initializing a background isolate running a + * callback dispatcher, used to invoke Dart callbacks while backgrounded. + */ +public class FlutterBackgroundExecutor implements MethodCallHandler { + private static final String TAG = "FlutterBackgroundExecutor"; + private static final String CALLBACK_HANDLE_KEY = "callback_handle"; + private static PluginRegistrantCallback pluginRegistrantCallback; + + /** + * The {@link MethodChannel} that connects the Android side of this plugin with the background + * Dart isolate that was created by this plugin. + */ + private MethodChannel backgroundChannel; + + private FlutterEngine backgroundFlutterEngine; + + private AtomicBoolean isCallbackDispatcherReady = new AtomicBoolean(false); + + /** + * Sets the {@code PluginRegistrantCallback} used to register plugins with the newly spawned + * isolate. + * + *

Note: this is only necessary for applications using the V1 engine embedding API as plugins + * are automatically registered via reflection in the V2 engine embedding API. If not set, alarm + * callbacks will not be able to utilize functionality from other plugins. + */ + public static void setPluginRegistrant(PluginRegistrantCallback callback) { + pluginRegistrantCallback = callback; + } + + /** + * Sets the Dart callback handle for the Dart method that is responsible for initializing the + * background Dart isolate, preparing it to receive Dart callback tasks requests. + */ + public static void setCallbackDispatcher(Context context, long callbackHandle) { + SharedPreferences prefs = context.getSharedPreferences(AlarmService.SHARED_PREFERENCES_KEY, 0); + prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); + } + + /** Returns true when the background isolate has started and is ready to handle alarms. */ + public boolean isRunning() { + return isCallbackDispatcherReady.get(); + } + + private void onInitialized() { + isCallbackDispatcherReady.set(true); + AlarmService.onInitialized(); + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + String method = call.method; + Object arguments = call.arguments; + try { + if (method.equals("AlarmService.initialized")) { + // This message is sent by the background method channel as soon as the background isolate + // is running. From this point forward, the Android side of this plugin can send + // callback handles through the background method channel, and the Dart side will execute + // the Dart methods corresponding to those callback handles. + onInitialized(); + result.success(true); + } else { + result.notImplemented(); + } + } catch (PluginRegistrantException e) { + result.error("error", "AlarmManager error: " + e.getMessage(), null); + } + } + + /** + * Starts running a background Dart isolate within a new {@link FlutterEngine} using a previously + * used entrypoint. + * + *

The isolate is configured as follows: + * + *

    + *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. + *
  • Entrypoint: The Dart method used the last time this plugin was initialized in the + * foreground. + *
  • Run args: none. + *
+ * + *

Preconditions: + * + *

    + *
  • The given callback must correspond to a registered Dart callback. If the handle does not + * resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ + public void startBackgroundIsolate(Context context) { + if (!isRunning()) { + SharedPreferences p = context.getSharedPreferences(AlarmService.SHARED_PREFERENCES_KEY, 0); + long callbackHandle = p.getLong(CALLBACK_HANDLE_KEY, 0); + startBackgroundIsolate(context, callbackHandle); + } + } + + /** + * Starts running a background Dart isolate within a new {@link FlutterEngine}. + * + *

The isolate is configured as follows: + * + *

    + *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. + *
  • Entrypoint: The Dart method represented by {@code callbackHandle}. + *
  • Run args: none. + *
+ * + *

Preconditions: + * + *

    + *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the + * handle does not resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ + public void startBackgroundIsolate(Context context, long callbackHandle) { + if (backgroundFlutterEngine != null) { + Log.e(TAG, "Background isolate already started"); + return; + } + + FlutterCallbackInformation flutterCallback = + FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + if (flutterCallback == null) { + Log.e(TAG, "Fatal: failed to find callback"); + return; + } + Log.i(TAG, "Starting AlarmService..."); + String appBundlePath = FlutterMain.findAppBundlePath(context); + AssetManager assets = context.getAssets(); + if (appBundlePath != null && !isRunning()) { + backgroundFlutterEngine = new FlutterEngine(context); + DartExecutor executor = backgroundFlutterEngine.getDartExecutor(); + initializeMethodChannel(executor); + DartCallback dartCallback = new DartCallback(assets, appBundlePath, flutterCallback); + + executor.executeDartCallback(dartCallback); + + // The pluginRegistrantCallback should only be set in the V1 embedding as + // plugin registration is done via reflection in the V2 embedding. + if (pluginRegistrantCallback != null) { + pluginRegistrantCallback.registerWith(new ShimPluginRegistry(backgroundFlutterEngine)); + } + } + } + + /** + * Executes the desired Dart callback in a background Dart isolate. + * + *

The given {@code intent} should contain a {@code long} extra called "callbackHandle", which + * corresponds to a callback registered with the Dart VM. + */ + public void executeDartCallbackInBackgroundIsolate(Intent intent, final CountDownLatch latch) { + // Grab the handle for the callback associated with this alarm. Pay close + // attention to the type of the callback handle as storing this value in a + // variable of the wrong size will cause the callback lookup to fail. + long callbackHandle = intent.getLongExtra("callbackHandle", 0); + + // If another thread is waiting, then wake that thread when the callback returns a result. + MethodChannel.Result result = null; + if (latch != null) { + result = + new MethodChannel.Result() { + @Override + public void success(Object result) { + latch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + latch.countDown(); + } + + @Override + public void notImplemented() { + latch.countDown(); + } + }; + } + + // Handle the alarm event in Dart. Note that for this plugin, we don't + // care about the method name as we simply lookup and invoke the callback + // provided. + backgroundChannel.invokeMethod( + "invokeAlarmManagerCallback", + new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, + result); + } + + private void initializeMethodChannel(BinaryMessenger isolate) { + // backgroundChannel is the channel responsible for receiving the following messages from + // the background isolate that was setup by this plugin: + // - "AlarmService.initialized" + // + // This channel is also responsible for sending requests from Android to Dart to execute Dart + // callbacks in the background isolate. + backgroundChannel = + new MethodChannel( + isolate, + "plugins.flutter.io/android_alarm_manager_background", + JSONMethodCodec.INSTANCE); + backgroundChannel.setMethodCallHandler(this); + } +} diff --git a/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java new file mode 100644 index 000000000000..6b69d39de003 --- /dev/null +++ b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java @@ -0,0 +1,15 @@ +// 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. + +package io.flutter.plugins.androidalarmmanagerexample; + +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 MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml b/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml index 7d87c6e1aae0..c67a8dd8f4f8 100644 --- a/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml +++ b/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,15 @@ android:name=".Application" android:label="android_alarm_manager_example" android:icon="@mipmap/ic_launcher"> + + - diff --git a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..17a37b8dd156 --- /dev/null +++ b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/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.androidalarmmanagerexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + public static final String TAG = "AlarmExampleMainActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java index 3d9afa5235c3..2c80708c4e94 100644 --- a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java +++ b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java @@ -1,15 +1,24 @@ +// 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. + package io.flutter.plugins.androidalarmmanagerexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import dev.flutter.plugins.e2e.E2EPlugin; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugins.androidalarmmanager.AndroidAlarmManagerPlugin; +import io.flutter.plugins.pathprovider.PathProviderPlugin; public class MainActivity extends FlutterActivity { - public static final String TAG = "AlarmExampleMainActivity"; - + // TODO(bkonyi): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694 @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); + flutterEngine.getPlugins().add(new AndroidAlarmManagerPlugin()); + flutterEngine.getPlugins().add(new E2EPlugin()); + PathProviderPlugin.registerWith( + shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); } } diff --git a/packages/android_alarm_manager/example/android/gradle.properties b/packages/android_alarm_manager/example/android/gradle.properties index 53ae0ae470eb..b6e61b62b903 100644 --- a/packages/android_alarm_manager/example/android/gradle.properties +++ b/packages/android_alarm_manager/example/android/gradle.properties @@ -1,3 +1,4 @@ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/packages/android_alarm_manager/example/android/settings_aar.gradle b/packages/android_alarm_manager/example/android/settings_aar.gradle new file mode 100644 index 000000000000..e7b4def49cb5 --- /dev/null +++ b/packages/android_alarm_manager/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart index 5e20364312bb..12aad9b001a9 100644 --- a/packages/android_alarm_manager/example/lib/main.dart +++ b/packages/android_alarm_manager/example/lib/main.dart @@ -18,6 +18,8 @@ Future main() async { final int periodicID = 0; final int oneShotID = 1; + WidgetsFlutterBinding.ensureInitialized(); + // Start the AlarmManager service. await AndroidAlarmManager.initialize(); @@ -27,7 +29,7 @@ Future main() async { Text('See device log for output', textDirection: TextDirection.ltr))); await AndroidAlarmManager.periodic( const Duration(seconds: 5), periodicID, printPeriodic, - wakeup: true); + wakeup: true, exact: true); await AndroidAlarmManager.oneShot( const Duration(seconds: 5), oneShotID, printOneShot); } diff --git a/packages/android_alarm_manager/example/pubspec.yaml b/packages/android_alarm_manager/example/pubspec.yaml index 392c03dc8902..3046f16a1405 100644 --- a/packages/android_alarm_manager/example/pubspec.yaml +++ b/packages/android_alarm_manager/example/pubspec.yaml @@ -6,8 +6,13 @@ dependencies: sdk: flutter android_alarm_manager: path: ../ + e2e: ^0.2.1 + path_provider: ^1.3.1 + dev_dependencies: + flutter_driver: + sdk: flutter flutter_test: sdk: flutter diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart new file mode 100644 index 000000000000..8359bfd59ef2 --- /dev/null +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart @@ -0,0 +1,101 @@ +// 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:android_alarm_manager/android_alarm_manager.dart'; +import 'package:e2e/e2e.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider/path_provider.dart'; + +// From https://flutter.dev/docs/cookbook/persistence/reading-writing-files +Future get _localPath async { + final Directory directory = await getTemporaryDirectory(); + return directory.path; +} + +Future get _localFile async { + final String path = await _localPath; + return File('$path/counter.txt'); +} + +Future writeCounter(int counter) async { + final File file = await _localFile; + + // Write the file. + return file.writeAsString('$counter'); +} + +Future readCounter() async { + try { + final File file = await _localFile; + + // Read the file. + final String contents = await file.readAsString(); + + return int.parse(contents); + // ignore: unused_catch_clause + } on FileSystemException catch (e) { + // If encountering an error, return 0. + return 0; + } +} + +Future incrementCounter() async { + final int value = await readCounter(); + print('incrementCounter to: ${value + 1}'); + await writeCounter(value + 1); +} + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + print('main'); + setUp(() async { + await AndroidAlarmManager.initialize(); + }); + + group('oneshot', () { + testWidgets('cancelled before it fires', (WidgetTester tester) async { + final int alarmId = 0; + final int startingValue = await readCounter(); + await AndroidAlarmManager.oneShot( + const Duration(seconds: 1), alarmId, incrementCounter); + expect(await AndroidAlarmManager.cancel(alarmId), isTrue); + await Future.delayed(const Duration(seconds: 4)); + expect(await readCounter(), startingValue); + }); + + testWidgets('cancelled after it fires', (WidgetTester tester) async { + final int alarmId = 1; + final int startingValue = await readCounter(); + await AndroidAlarmManager.oneShot( + const Duration(seconds: 1), alarmId, incrementCounter, + exact: true, wakeup: true); + await Future.delayed(const Duration(seconds: 2)); + // poll until file is updated + while (await readCounter() == startingValue) { + await Future.delayed(const Duration(seconds: 1)); + } + expect(await readCounter(), startingValue + 1); + expect(await AndroidAlarmManager.cancel(alarmId), isTrue); + }); + }); + + testWidgets('periodic', (WidgetTester tester) async { + final int alarmId = 2; + final int startingValue = await readCounter(); + await AndroidAlarmManager.periodic( + const Duration(seconds: 1), alarmId, incrementCounter, + wakeup: true, exact: true); + // poll until file is updated + while (await readCounter() < startingValue + 2) { + await Future.delayed(const Duration(seconds: 1)); + } + expect(await readCounter(), startingValue + 2); + expect(await AndroidAlarmManager.cancel(alarmId), isTrue); + await Future.delayed(const Duration(seconds: 3)); + expect(await readCounter(), startingValue + 2); + }); +} diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart new file mode 100644 index 000000000000..eea5e8abc15f --- /dev/null +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -0,0 +1,41 @@ +// 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'; +import 'package:vm_service_client/vm_service_client.dart'; + +Future> resumeIsolatesOnPause( + FlutterDriver driver) async { + final VM vm = await driver.serviceClient.getVM(); + for (VMIsolateRef isolateRef in vm.isolates) { + final VMIsolate isolate = await isolateRef.load(); + if (isolate.isPaused) { + await isolate.resume(); + } + } + return driver.serviceClient.onIsolateRunnable + .asBroadcastStream() + .listen((VMIsolateRef isolateRef) async { + final VMIsolate isolate = await isolateRef.load(); + if (isolate.isPaused) { + await isolate.resume(); + } + }); +} + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + // flutter drive causes isolates to be paused on spawn. The background isolate + // for this plugin will need to be resumed for the test to pass. + final StreamSubscription subscription = + await resumeIsolatesOnPause(driver); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 5)); + await driver.close(); + await subscription.cancel(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index f31b4bc87525..b8afa134472c 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -13,17 +13,16 @@ const String _backgroundName = 'plugins.flutter.io/android_alarm_manager_background'; // This is the entrypoint for the background isolate. Since we can only enter -// an isolate once, we setup a MethodChannel to listen for method invokations +// an isolate once, we setup a MethodChannel to listen for method invocations // from the native portion of the plugin. This allows for the plugin to perform // any necessary processing in Dart (e.g., populating a custom object) before // invoking the provided callback. void _alarmManagerCallbackDispatcher() { - const MethodChannel _channel = - MethodChannel(_backgroundName, JSONMethodCodec()); - - // Setup Flutter state needed for MethodChannels. + // Initialize state necessary for MethodChannels. WidgetsFlutterBinding.ensureInitialized(); + const MethodChannel _channel = + MethodChannel(_backgroundName, JSONMethodCodec()); // This is where the magic happens and we handle background events from the // native portion of the plugin. _channel.setMethodCallHandler((MethodCall call) async { diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 29a9961b448d..9239fa41b6ef 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -1,7 +1,7 @@ name: android_alarm_manager description: Flutter plugin for accessing the Android AlarmManager service, and running Dart code in the background when alarms fire. -version: 0.4.4+3 +version: 0.4.5 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager @@ -15,10 +15,12 @@ dev_dependencies: flutter: plugin: - androidPackage: io.flutter.plugins.androidalarmmanager - pluginClass: AndroidAlarmManagerPlugin - iosPrefix: FLT + platforms: + android: + package: io.flutter.plugins.androidalarmmanager + pluginClass: AndroidAlarmManagerPlugin environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" + # TODO(bkonyi): set minimum Flutter version to next stable release flutter: ">=1.2.0 <2.0.0" From 7af9a1a385fe632d5a8b7b698d8b29e2e997326a Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 27 Nov 2019 15:24:33 -0800 Subject: [PATCH 46/47] Implement shared_preferences on top of platform interface (#2325) * implement shared_preferences on top of platform interface --- .gitignore | 1 + .../shared_preferences/analysis_options.yaml | 11 -- .../shared_preferences/CHANGELOG.md | 5 + .../shared_preferences/example/lib/main.dart | 5 +- .../test_driver/shared_preferences_e2e.dart | 22 ++- .../shared_preferences_e2e_test.dart | 2 +- .../lib/shared_preferences.dart | 37 ++--- .../shared_preferences/pubspec.yaml | 3 +- .../test/shared_preferences_test.dart | 131 +++++++++++------- script/build_all_plugins_app.sh | 1 + 10 files changed, 117 insertions(+), 101 deletions(-) delete mode 100644 packages/shared_preferences/analysis_options.yaml diff --git a/.gitignore b/.gitignore index 734169649fd3..54133a3b3a84 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ keystore.properties gradlew gradlew.bat gradle-wrapper.jar +.flutter-plugins-dependencies *.iml GeneratedPluginRegistrant.h diff --git a/packages/shared_preferences/analysis_options.yaml b/packages/shared_preferences/analysis_options.yaml deleted file mode 100644 index 35344bfb898c..000000000000 --- a/packages/shared_preferences/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - curly_braces_in_flow_control_structures: ignore - unawaited_futures: ignore diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md index 15a50475078d..487ec1db8a33 100644 --- a/packages/shared_preferences/shared_preferences/CHANGELOG.md +++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.5.4+8 + +* Switch `package:shared_preferences` to `package:shared_preferences_platform_interface`. + No code changes are necessary in Flutter apps. This is not a breaking change. + ## 0.5.4+7 * Restructure the project for Web support. diff --git a/packages/shared_preferences/shared_preferences/example/lib/main.dart b/packages/shared_preferences/shared_preferences/example/lib/main.dart index 5c7f0b0fda12..46daeff6706f 100644 --- a/packages/shared_preferences/shared_preferences/example/lib/main.dart +++ b/packages/shared_preferences/shared_preferences/example/lib/main.dart @@ -67,13 +67,14 @@ class SharedPreferencesDemoState extends State { case ConnectionState.waiting: return const CircularProgressIndicator(); default: - if (snapshot.hasError) + if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); - else + } else { return Text( 'Button tapped ${snapshot.data} time${snapshot.data == 1 ? '' : 's'}.\n\n' 'This should persist across restarts.', ); + } } })), floatingActionButton: FloatingActionButton( diff --git a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart index 1c09e2eb867e..b693df2131ed 100644 --- a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart +++ b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e.dart @@ -63,23 +63,21 @@ void main() { test('removing', () async { const String key = 'testKey'; - preferences - ..setString(key, kTestValues['flutter.String']) - ..setBool(key, kTestValues['flutter.bool']) - ..setInt(key, kTestValues['flutter.int']) - ..setDouble(key, kTestValues['flutter.double']) - ..setStringList(key, kTestValues['flutter.List']); + 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 { - preferences - ..setString('String', kTestValues['flutter.String']) - ..setBool('bool', kTestValues['flutter.bool']) - ..setInt('int', kTestValues['flutter.int']) - ..setDouble('double', kTestValues['flutter.double']) - ..setStringList('List', kTestValues['flutter.List']); + 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); diff --git a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart index ff6e9ce74ad9..f3aa9e218d82 100644 --- a/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart +++ b/packages/shared_preferences/shared_preferences/example/test_driver/shared_preferences_e2e_test.dart @@ -10,6 +10,6 @@ Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); + await driver.close(); exit(result == 'pass' ? 0 : 1); } diff --git a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart index c82116a57437..62160dee20fd 100644 --- a/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart +++ b/packages/shared_preferences/shared_preferences/lib/shared_preferences.dart @@ -4,11 +4,9 @@ import 'dart:async'; -import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; -const MethodChannel _kChannel = - MethodChannel('plugins.flutter.io/shared_preferences'); +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; /// Wraps NSUserDefaults (on iOS) and SharedPreferences (on Android), providing /// a persistent store for simple data. @@ -20,6 +18,9 @@ class SharedPreferences { static const String _prefix = 'flutter.'; static Completer _completer; + static SharedPreferencesStorePlatform get _store => + SharedPreferencesStorePlatform.instance; + /// Loads and parses the [SharedPreferences] for this app from disk. /// /// Because this is reading from disk, it shouldn't be awaited in @@ -124,14 +125,10 @@ class SharedPreferences { Future remove(String key) => _setValue(null, key, null); Future _setValue(String valueType, String key, Object value) { - final Map params = { - 'key': '$_prefix$key', - }; + final String prefixedKey = '$_prefix$key'; if (value == null) { _preferenceCache.remove(key); - return _kChannel - .invokeMethod('remove', params) - .then((dynamic result) => result); + return _store.remove(prefixedKey); } else { if (value is List) { // Make a copy of the list so that later mutations won't propagate @@ -139,22 +136,19 @@ class SharedPreferences { } else { _preferenceCache[key] = value; } - params['value'] = value; - return _kChannel - .invokeMethod('set$valueType', params) - .then((dynamic result) => result); + return _store.setValue(valueType, prefixedKey, value); } } /// Always returns true. /// On iOS, synchronize is marked deprecated. On Android, we commit every set. @deprecated - Future commit() async => await _kChannel.invokeMethod('commit'); + Future commit() async => true; /// Completes with true once the user preferences for the app has been cleared. - Future clear() async { + Future clear() { _preferenceCache.clear(); - return await _kChannel.invokeMethod('clear'); + return _store.clear(); } /// Fetches the latest values from the host platform. @@ -169,8 +163,7 @@ class SharedPreferences { } static Future> _getSharedPreferencesMap() async { - final Map fromSystem = - await _kChannel.invokeMapMethod('getAll'); + final Map fromSystem = await _store.getAll(); assert(fromSystem != null); // Strip the flutter. prefix from the returned preferences. final Map preferencesMap = {}; @@ -194,12 +187,8 @@ class SharedPreferences { } return MapEntry(newKey, value); }); - _kChannel.setMockMethodCallHandler((MethodCall methodCall) async { - if (methodCall.method == 'getAll') { - return newValues; - } - return null; - }); + SharedPreferencesStorePlatform.instance = + InMemorySharedPreferencesStore.withData(newValues); _completer = null; } } diff --git a/packages/shared_preferences/shared_preferences/pubspec.yaml b/packages/shared_preferences/shared_preferences/pubspec.yaml index 62853949d055..c9f6d335b869 100644 --- a/packages/shared_preferences/shared_preferences/pubspec.yaml +++ b/packages/shared_preferences/shared_preferences/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for reading and writing simple key-value pairs. Wraps NSUserDefaults on iOS and SharedPreferences on Android. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/shared_preferences/shared_preferences -version: 0.5.4+7 +version: 0.5.4+8 flutter: plugin: @@ -15,6 +15,7 @@ dependencies: meta: ^1.0.4 flutter: sdk: flutter + shared_preferences_platform_interface: ^1.0.0 dev_dependencies: flutter_test: diff --git a/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart index d171f7ae7f05..b219774d1992 100755 --- a/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart +++ b/packages/shared_preferences/shared_preferences/test/shared_preferences_test.dart @@ -5,15 +5,12 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$SharedPreferences', () { - const MethodChannel channel = MethodChannel( - 'plugins.flutter.io/shared_preferences', - ); - + group('SharedPreferences', () { const Map kTestValues = { 'flutter.String': 'hello world', 'flutter.bool': true, @@ -30,23 +27,18 @@ void main() { 'flutter.List': ['baz', 'quox'], }; - final List log = []; + FakeSharedPreferencesStore store; SharedPreferences preferences; setUp(() async { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - if (methodCall.method == 'getAll') { - return kTestValues; - } - return null; - }); + store = FakeSharedPreferencesStore(kTestValues); + SharedPreferencesStorePlatform.instance = store; preferences = await SharedPreferences.getInstance(); - log.clear(); + store.log.clear(); }); - tearDown(() { - preferences.clear(); + tearDown(() async { + await preferences.clear(); }); test('reading', () async { @@ -60,7 +52,7 @@ void main() { expect(preferences.getInt('int'), kTestValues['flutter.int']); expect(preferences.getDouble('double'), kTestValues['flutter.double']); expect(preferences.getStringList('List'), kTestValues['flutter.List']); - expect(log, []); + expect(store.log, []); }); test('writing', () async { @@ -72,56 +64,60 @@ void main() { preferences.setStringList('List', kTestValues2['flutter.List']) ]); expect( - log, + store.log, [ - isMethodCall('setString', arguments: { - 'key': 'flutter.String', - 'value': kTestValues2['flutter.String'] - }), - isMethodCall('setBool', arguments: { - 'key': 'flutter.bool', - 'value': kTestValues2['flutter.bool'] - }), - isMethodCall('setInt', arguments: { - 'key': 'flutter.int', - 'value': kTestValues2['flutter.int'] - }), - isMethodCall('setDouble', arguments: { - 'key': 'flutter.double', - 'value': kTestValues2['flutter.double'] - }), - isMethodCall('setStringList', arguments: { - 'key': 'flutter.List', - 'value': kTestValues2['flutter.List'] - }), + isMethodCall('setValue', arguments: [ + 'String', + 'flutter.String', + kTestValues2['flutter.String'], + ]), + isMethodCall('setValue', arguments: [ + 'Bool', + 'flutter.bool', + kTestValues2['flutter.bool'], + ]), + isMethodCall('setValue', arguments: [ + 'Int', + 'flutter.int', + kTestValues2['flutter.int'], + ]), + isMethodCall('setValue', arguments: [ + 'Double', + 'flutter.double', + kTestValues2['flutter.double'], + ]), + isMethodCall('setValue', arguments: [ + 'StringList', + 'flutter.List', + kTestValues2['flutter.List'], + ]), ], ); - log.clear(); + store.log.clear(); 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']); - expect(log, equals([])); + expect(store.log, equals([])); }); test('removing', () async { const String key = 'testKey'; - preferences - ..setString(key, null) - ..setBool(key, null) - ..setInt(key, null) - ..setDouble(key, null) - ..setStringList(key, null); + await preferences.setString(key, null); + await preferences.setBool(key, null); + await preferences.setInt(key, null); + await preferences.setDouble(key, null); + await preferences.setStringList(key, null); await preferences.remove(key); expect( - log, + store.log, List.filled( 6, isMethodCall( 'remove', - arguments: {'key': 'flutter.$key'}, + arguments: 'flutter.$key', ), growable: true, )); @@ -132,7 +128,7 @@ void main() { expect(false, preferences.containsKey(key)); - preferences.setString(key, 'test'); + await preferences.setString(key, 'test'); expect(true, preferences.containsKey(key)); }); @@ -143,7 +139,7 @@ void main() { expect(preferences.getInt('int'), null); expect(preferences.getDouble('double'), null); expect(preferences.getStringList('List'), null); - expect(log, [isMethodCall('clear', arguments: null)]); + expect(store.log, [isMethodCall('clear', arguments: null)]); }); test('reloading', () async { @@ -207,3 +203,38 @@ void main() { expect(value, 'foo'); }); } + +class FakeSharedPreferencesStore implements SharedPreferencesStorePlatform { + FakeSharedPreferencesStore(Map data) + : backend = InMemorySharedPreferencesStore.withData(data); + + final InMemorySharedPreferencesStore backend; + final List log = []; + + @override + bool get isMock => true; + + @override + Future clear() { + log.add(MethodCall('clear')); + return backend.clear(); + } + + @override + Future> getAll() { + log.add(MethodCall('getAll')); + return backend.getAll(); + } + + @override + Future remove(String key) { + log.add(MethodCall('remove', key)); + return backend.remove(key); + } + + @override + Future setValue(String valueType, String key, Object value) { + log.add(MethodCall('setValue', [valueType, key, value])); + return backend.setValue(valueType, key, value); + } +} diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index 1028ee607386..903924fd322a 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -15,6 +15,7 @@ readonly EXCLUDED_PLUGINS_LIST=( "url_launcher_platform_interface" "google_sign_in_platform_interface" "video_player_platform_interface" + "shared_preferences_platform_interface" ) # Comma-separated string of the list above readonly EXCLUDED=$(IFS=, ; echo "${EXCLUDED_PLUGINS_LIST[*]}") From 3d57cff403507412c530a3785f0502ea19458275 Mon Sep 17 00:00:00 2001 From: Ben Hagen Date: Thu, 28 Nov 2019 02:46:25 +0100 Subject: [PATCH 47/47] [video_player] Use platform interface (#2276) * [video_player] Use platform interface * Move tests (leftover from part1) * Merge master * Export DurationRange and DataSourceType * Export VideoFormat * ignore: unused_element * Format * Update video_player_platform_interface dependency --- .../video_player/video_player/CHANGELOG.md | 4 + .../example/test_driver/video_player.dart | 0 .../test_driver/video_player_test.dart | 0 .../video_player/lib/video_player.dart | 226 +++++------------- .../video_player/video_player/pubspec.yaml | 6 +- .../video_player/test/video_player_test.dart | 1 + .../lib/video_player_platform_interface.dart | 50 +++- 7 files changed, 113 insertions(+), 174 deletions(-) rename packages/video_player/{ => video_player}/example/test_driver/video_player.dart (100%) rename packages/video_player/{ => video_player}/example/test_driver/video_player_test.dart (100%) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index d4824c461a55..50c528dbefc1 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.4 + +* Port plugin code to use the federated Platform Interface, instead of a MethodChannel directly. + ## 0.10.3+3 * Add DartDocs and unit tests. diff --git a/packages/video_player/example/test_driver/video_player.dart b/packages/video_player/video_player/example/test_driver/video_player.dart similarity index 100% rename from packages/video_player/example/test_driver/video_player.dart rename to packages/video_player/video_player/example/test_driver/video_player.dart diff --git a/packages/video_player/example/test_driver/video_player_test.dart b/packages/video_player/video_player/example/test_driver/video_player_test.dart similarity index 100% rename from packages/video_player/example/test_driver/video_player_test.dart rename to packages/video_player/video_player/example/test_driver/video_player_test.dart diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index f92693e79eda..3a4768fb256c 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -5,77 +5,19 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; import 'package:meta/meta.dart'; -final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer') - // This will clear all open videos on the platform when a full restart is - // performed. - ..invokeMethod('init'); +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +export 'package:video_player_platform_interface/video_player_platform_interface.dart' + show DurationRange, DataSourceType, VideoFormat; -/// Describes a discrete segment of time within a video using a [start] and -/// [end] [Duration]. -class DurationRange { - /// Trusts that the given [start] and [end] are actually in order. They should - /// both be non-null. - DurationRange(this.start, this.end); - - /// The beginning of the segment described relative to the beginning of the - /// entire video. Should be shorter than or equal to [end]. - /// - /// For example, if the entire video is 4 minutes long and the range is from - /// 1:00-2:00, this should be a `Duration` of one minute. - final Duration start; - - /// The end of the segment described as a duration relative to the beginning of - /// the entire video. This is expected to be non-null and longer than or equal - /// to [start]. - /// - /// For example, if the entire video is 4 minutes long and the range is from - /// 1:00-2:00, this should be a `Duration` of two minutes. - final Duration end; - - /// Assumes that [duration] is the total length of the video that this - /// DurationRange is a segment form. It returns the percentage that [start] is - /// through the entire video. - /// - /// For example, assume that the entire video is 4 minutes long. If [start] has - /// a duration of one minute, this will return `0.25` since the DurationRange - /// starts 25% of the way through the video's total length. - double startFraction(Duration duration) { - return start.inMilliseconds / duration.inMilliseconds; - } - - /// Assumes that [duration] is the total length of the video that this - /// DurationRange is a segment form. It returns the percentage that [start] is - /// through the entire video. - /// - /// For example, assume that the entire video is 4 minutes long. If [end] has a - /// duration of two minutes, this will return `0.5` since the DurationRange - /// ends 50% of the way through the video's total length. - double endFraction(Duration duration) { - return end.inMilliseconds / duration.inMilliseconds; - } - - @override - String toString() => '$runtimeType(start: $start, end: $end)'; -} - -/// The file format of the given video. -enum VideoFormat { - /// Dynamic Adaptive Streaming over HTTP, also known as MPEG-DASH. - dash, - - /// HTTP Live Streaming. - hls, - - /// Smooth Streaming. - ss, - - /// Any format other than the other ones defined in this enum. - other -} +// This will clear all open videos on the platform when a full restart is +// performed. +// ignore: unused_element +final VideoPlayerPlatform _ = VideoPlayerPlatform.instance..init(); /// The duration, current position, buffering state, error state and settings /// of a [VideoPlayerController]. @@ -187,20 +129,6 @@ class VideoPlayerValue { } } -/// The way in which the video was originally loaded. This has nothing to do -/// with the video's file type. It's just the place from which the video is -/// fetched from. -enum DataSourceType { - /// The video was included in the app's asset files. - asset, - - /// The video was downloaded from the internet. - network, - - /// The video was loaded off of the local filesystem. - file -} - /// Controls a platform video player, and provides updates when the state is /// changing. /// @@ -277,75 +205,66 @@ class VideoPlayerController extends ValueNotifier { _lifeCycleObserver = _VideoAppLifeCycleObserver(this); _lifeCycleObserver.initialize(); _creatingCompleter = Completer(); - Map dataSourceDescription; + + DataSource dataSourceDescription; switch (dataSourceType) { case DataSourceType.asset: - dataSourceDescription = { - 'asset': dataSource, - 'package': package - }; + dataSourceDescription = DataSource( + sourceType: DataSourceType.asset, + asset: dataSource, + package: package, + ); break; case DataSourceType.network: - dataSourceDescription = { - 'uri': dataSource, - 'formatHint': _videoFormatStringMap[formatHint] - }; + dataSourceDescription = DataSource( + sourceType: DataSourceType.network, + uri: dataSource, + formatHint: formatHint, + ); break; case DataSourceType.file: - dataSourceDescription = {'uri': dataSource}; + dataSourceDescription = DataSource( + sourceType: DataSourceType.file, + uri: dataSource, + ); break; } - final Map response = - await _channel.invokeMapMethod( - 'create', - dataSourceDescription, - ); - _textureId = response['textureId']; + _textureId = + await VideoPlayerPlatform.instance.create(dataSourceDescription); _creatingCompleter.complete(null); final Completer initializingCompleter = Completer(); - DurationRange toDurationRange(dynamic value) { - final List pair = value; - return DurationRange( - Duration(milliseconds: pair[0]), - Duration(milliseconds: pair[1]), - ); - } - - void eventListener(dynamic event) { + void eventListener(VideoEvent event) { if (_isDisposed) { return; } - final Map map = event; - switch (map['event']) { - case 'initialized': + switch (event.eventType) { + case VideoEventType.initialized: value = value.copyWith( - duration: Duration(milliseconds: map['duration']), - size: Size(map['width']?.toDouble() ?? 0.0, - map['height']?.toDouble() ?? 0.0), + duration: event.duration, + size: event.size, ); initializingCompleter.complete(null); _applyLooping(); _applyVolume(); _applyPlayPause(); break; - case 'completed': + case VideoEventType.completed: value = value.copyWith(isPlaying: false, position: value.duration); _timer?.cancel(); break; - case 'bufferingUpdate': - final List values = map['values']; - value = value.copyWith( - buffered: values.map(toDurationRange).toList(), - ); + case VideoEventType.bufferingUpdate: + value = value.copyWith(buffered: event.buffered); break; - case 'bufferingStart': + case VideoEventType.bufferingStart: value = value.copyWith(isBuffering: true); break; - case 'bufferingEnd': + case VideoEventType.bufferingEnd: value = value.copyWith(isBuffering: false); break; + case VideoEventType.unknown: + break; } } @@ -355,16 +274,12 @@ class VideoPlayerController extends ValueNotifier { _timer?.cancel(); } - _eventSubscription = _eventChannelFor(_textureId) - .receiveBroadcastStream() + _eventSubscription = VideoPlayerPlatform.instance + .videoEventsFor(_textureId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } - EventChannel _eventChannelFor(int textureId) { - return EventChannel('flutter.io/videoPlayer/videoEvents$textureId'); - } - @override Future dispose() async { if (_creatingCompleter != null) { @@ -373,10 +288,7 @@ class VideoPlayerController extends ValueNotifier { _isDisposed = true; _timer?.cancel(); await _eventSubscription?.cancel(); - await _channel.invokeMethod( - 'dispose', - {'textureId': _textureId}, - ); + await VideoPlayerPlatform.instance.dispose(_textureId); } _lifeCycleObserver.dispose(); } @@ -411,10 +323,7 @@ class VideoPlayerController extends ValueNotifier { if (!value.initialized || _isDisposed) { return; } - _channel.invokeMethod( - 'setLooping', - {'textureId': _textureId, 'looping': value.isLooping}, - ); + VideoPlayerPlatform.instance.setLooping(_textureId, value.isLooping); } Future _applyPlayPause() async { @@ -422,10 +331,7 @@ class VideoPlayerController extends ValueNotifier { return; } if (value.isPlaying) { - await _channel.invokeMethod( - 'play', - {'textureId': _textureId}, - ); + VideoPlayerPlatform.instance.play(_textureId); _timer = Timer.periodic( const Duration(milliseconds: 500), (Timer timer) async { @@ -441,10 +347,7 @@ class VideoPlayerController extends ValueNotifier { ); } else { _timer?.cancel(); - await _channel.invokeMethod( - 'pause', - {'textureId': _textureId}, - ); + VideoPlayerPlatform.instance.pause(_textureId); } } @@ -452,10 +355,7 @@ class VideoPlayerController extends ValueNotifier { if (!value.initialized || _isDisposed) { return; } - await _channel.invokeMethod( - 'setVolume', - {'textureId': _textureId, 'volume': value.volume}, - ); + VideoPlayerPlatform.instance.setVolume(_textureId, value.volume); } /// The position in the current video. @@ -463,12 +363,7 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposed) { return null; } - return Duration( - milliseconds: await _channel.invokeMethod( - 'position', - {'textureId': _textureId}, - ), - ); + return await VideoPlayerPlatform.instance.getPosition(_textureId); } /// Sets the video's current timestamp to be at [moment]. The next @@ -476,20 +371,17 @@ class VideoPlayerController extends ValueNotifier { /// /// If [moment] is outside of the video's full range it will be automatically /// and silently clamped. - Future seekTo(Duration moment) async { + Future seekTo(Duration position) async { if (_isDisposed) { return; } - if (moment > value.duration) { - moment = value.duration; - } else if (moment < const Duration()) { - moment = const Duration(); + if (position > value.duration) { + position = value.duration; + } else if (position < const Duration()) { + position = const Duration(); } - await _channel.invokeMethod('seekTo', { - 'textureId': _textureId, - 'location': moment.inMilliseconds, - }); - value = value.copyWith(position: moment); + VideoPlayerPlatform.instance.seekTo(_textureId, position); + value = value.copyWith(position: position); } /// Sets the audio volume of [this]. @@ -500,14 +392,6 @@ class VideoPlayerController extends ValueNotifier { value = value.copyWith(volume: volume.clamp(0.0, 1.0)); await _applyVolume(); } - - static const Map _videoFormatStringMap = - { - VideoFormat.ss: 'ss', - VideoFormat.hls: 'hls', - VideoFormat.dash: 'dash', - VideoFormat.other: 'other', - }; } class _VideoAppLifeCycleObserver extends Object with WidgetsBindingObserver { @@ -594,7 +478,9 @@ class _VideoPlayerState extends State { @override Widget build(BuildContext context) { - return _textureId == null ? Container() : Texture(textureId: _textureId); + return _textureId == null + ? Container() + : VideoPlayerPlatform.instance.buildView(_textureId); } } diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index cf85568de389..e191377e5c99 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -2,8 +2,8 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.3+3 -homepage: https://github.com/flutter/plugins/tree/master/packages/video_player +version: 0.10.4 +homepage: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player flutter: plugin: @@ -13,6 +13,8 @@ flutter: dependencies: meta: "^1.0.5" + video_player_platform_interface: ^1.0.1 + flutter: sdk: 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 10b5754bbad2..f0aac1280d48 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:video_player/video_player.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:video_player_platform_interface/video_player_platform_interface.dart'; class FakeController extends ValueNotifier implements VideoPlayerController { diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 031baf6f218d..46b04d4579a9 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -135,17 +135,33 @@ class DataSource { final String package; } +/// The way in which the video was originally loaded. This has nothing to do +/// with the video's file type. It's just the place from which the video is +/// fetched from. enum DataSourceType { + /// The video was included in the app's asset files. asset, + + /// The video was downloaded from the internet. network, - file, + + /// The video was loaded off of the local filesystem. + file } +/// The file format of the given video. enum VideoFormat { + /// Dynamic Adaptive Streaming over HTTP, also known as MPEG-DASH. dash, + + /// HTTP Live Streaming. hls, + + /// Smooth Streaming. ss, - other, + + /// Any format other than the other ones defined in this enum. + other } class VideoEvent { @@ -189,16 +205,46 @@ enum VideoEventType { unknown, } +/// Describes a discrete segment of time within a video using a [start] and +/// [end] [Duration]. class DurationRange { + /// Trusts that the given [start] and [end] are actually in order. They should + /// both be non-null. DurationRange(this.start, this.end); + /// The beginning of the segment described relative to the beginning of the + /// entire video. Should be shorter than or equal to [end]. + /// + /// For example, if the entire video is 4 minutes long and the range is from + /// 1:00-2:00, this should be a `Duration` of one minute. final Duration start; + + /// The end of the segment described as a duration relative to the beginning of + /// the entire video. This is expected to be non-null and longer than or equal + /// to [start]. + /// + /// For example, if the entire video is 4 minutes long and the range is from + /// 1:00-2:00, this should be a `Duration` of two minutes. final Duration end; + /// Assumes that [duration] is the total length of the video that this + /// DurationRange is a segment form. It returns the percentage that [start] is + /// through the entire video. + /// + /// For example, assume that the entire video is 4 minutes long. If [start] has + /// a duration of one minute, this will return `0.25` since the DurationRange + /// starts 25% of the way through the video's total length. double startFraction(Duration duration) { return start.inMilliseconds / duration.inMilliseconds; } + /// Assumes that [duration] is the total length of the video that this + /// DurationRange is a segment form. It returns the percentage that [start] is + /// through the entire video. + /// + /// For example, assume that the entire video is 4 minutes long. If [end] has a + /// duration of two minutes, this will return `0.5` since the DurationRange + /// ends 50% of the way through the video's total length. double endFraction(Duration duration) { return end.inMilliseconds / duration.inMilliseconds; }