diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 9ed7a78f1bb1..45a2235043e6 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.4+2 + +* Fix Android NullPointerException on devices with only front-facing camera. + ## 0.5.4+1 * Fix Android pause and resume video crash when executing in APIs below 24. diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java index e4613fb237c1..a7bb3b7d4914 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraUtils.java @@ -77,32 +77,31 @@ static CamcorderProfile getBestAvailableCamcorderProfileForResolutionPreset( // All of these cases deliberately fall through to get the best available profile. case max: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH); } case ultraHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_2160P)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_2160P); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_2160P); } case veryHigh: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_1080P)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_1080P); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_1080P); } case high: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_720P)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_720P); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_720P); } case medium: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_480P); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P); } case low: if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_QVGA); + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA); } default: - if (CamcorderProfile.hasProfile( - Integer.parseInt(cameraName), CamcorderProfile.QUALITY_LOW)) { - return CamcorderProfile.get(CamcorderProfile.QUALITY_LOW); + if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_LOW)) { + return CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW); } else { throw new IllegalArgumentException( "No capture session available for current capture session."); diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index c9e715225d59..e7c8632b042f 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.4+1 +version: 0.5.4+2 authors: - Flutter Team diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index addc8831efe3..e6f2ff00d02f 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.5.21+2 + +* Fix more `prefer_const_constructors` analyzer warnings in example app. + ## 0.5.21+1 * Fix `prefer_const_constructors` analyzer warnings in example app. diff --git a/packages/google_maps_flutter/example/lib/padding.dart b/packages/google_maps_flutter/example/lib/padding.dart index 599f8a564b6e..6c1fe38dbf3a 100644 --- a/packages/google_maps_flutter/example/lib/padding.dart +++ b/packages/google_maps_flutter/example/lib/padding.dart @@ -98,7 +98,7 @@ class MarkerIconsBodyState extends State { ), ), ), - Spacer(), + const Spacer(), Flexible( flex: 2, child: TextField( @@ -110,7 +110,7 @@ class MarkerIconsBodyState extends State { ), ), ), - Spacer(), + const Spacer(), Flexible( flex: 2, child: TextField( @@ -122,7 +122,7 @@ class MarkerIconsBodyState extends State { ), ), ), - Spacer(), + const Spacer(), Flexible( flex: 2, child: TextField( diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index a72cb71fdd78..7de1fd6bca7d 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+1 +version: 0.5.21+2 dependencies: flutter: diff --git a/packages/google_sign_in/CHANGELOG.md b/packages/google_sign_in/CHANGELOG.md index 92bf3230e06c..188167e53cae 100644 --- a/packages/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.0.8 + +* Get rid of `MethodCompleter` and serialize async actions using chained futures. + This prevents a bug when sign in methods are being used in error handling zones. + ## 4.0.7 * Switch from using `api` to `implementation` for dependency on `play-services-auth`, diff --git a/packages/google_sign_in/lib/google_sign_in.dart b/packages/google_sign_in/lib/google_sign_in.dart old mode 100755 new mode 100644 index ca6e6bbaf705..f1e1db21801e --- a/packages/google_sign_in/lib/google_sign_in.dart +++ b/packages/google_sign_in/lib/google_sign_in.dart @@ -230,50 +230,57 @@ class GoogleSignIn { } Future _ensureInitialized() { - if (_initialization == null) { - _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; + return _initialization ??= + channel.invokeMethod('init', { + 'signInOption': (signInOption ?? SignInOption.standard).toString(), + 'scopes': scopes ?? [], + 'hostedDomain': hostedDomain, + }) + ..catchError((dynamic _) { + // Invalidate initialization if it errored out. + _initialization = null; + }); } - /// Keeps track of the most recently scheduled method call. - _MethodCompleter _lastMethodCompleter; + /// The most recently scheduled method call. + Future _lastMethodCall; + + /// Returns a [Future] that completes with a success after [future], whether + /// it completed with a value or an error. + static Future _waitFor(Future future) { + final Completer completer = Completer(); + future.whenComplete(completer.complete).catchError((dynamic _) { + // Ignore if previous call completed with an error. + }); + return completer.future; + } /// Adds call to [method] in a queue for execution. /// /// At most one in flight call is allowed to prevent concurrent (out of order) /// updates to [currentUser] and [onCurrentUserChanged]. - Future _addMethodCall(String method) { - if (_lastMethodCompleter == null) { - _lastMethodCompleter = _MethodCompleter(method) - ..complete(_callMethod(method)); - return _lastMethodCompleter.future; + Future _addMethodCall(String method) 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 + // instead of making extra call to the native side. + const List kSignInMethods = [ + 'signIn', + 'signInSilently' + ]; + if (kSignInMethods.contains(method) && _currentUser != null) { + return _currentUser; + } else { + return _callMethod(method); + } + }); } - - final _MethodCompleter completer = _MethodCompleter(method); - _lastMethodCompleter.future.whenComplete(() { - // If after the last completed call currentUser is not null and requested - // method is a sign in method, 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) { - completer.complete(_currentUser); - } else { - completer.complete(_callMethod(method)); - } - }).catchError((dynamic _) { - // Ignore if previous call completed with an error. - }); - _lastMethodCompleter = completer; - return _lastMethodCompleter.future; + _lastMethodCall = _waitFor(response); + return response; } /// The currently signed in account, or null if the user is signed out. @@ -296,12 +303,17 @@ 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}) { - final Future result = _addMethodCall('signInSilently'); - if (suppressErrors) { - return result.catchError((dynamic _) => null); + Future signInSilently( + {bool suppressErrors = true}) async { + try { + return await _addMethodCall('signInSilently'); + } catch (_) { + if (suppressErrors) { + return null; + } else { + rethrow; + } } - return result; } /// Returns a future that resolves to whether a user is currently signed in. @@ -334,26 +346,3 @@ class GoogleSignIn { /// authentication. Future disconnect() => _addMethodCall('disconnect'); } - -class _MethodCompleter { - _MethodCompleter(this.method); - - final String method; - final Completer _completer = - Completer(); - - Future complete(FutureOr value) async { - if (value is Future) { - try { - _completer.complete(await value); - } catch (e, stacktrace) { - _completer.completeError(e, stacktrace); - } - } else { - _completer.complete(value); - } - } - - bool get isCompleted => _completer.isCompleted; - Future get future => _completer.future; -} diff --git a/packages/google_sign_in/pubspec.yaml b/packages/google_sign_in/pubspec.yaml old mode 100755 new mode 100644 index 6ed758895bf7..246a6389e39c --- a/packages/google_sign_in/pubspec.yaml +++ b/packages/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 -version: 4.0.7 +version: 4.0.8 flutter: plugin: diff --git a/packages/google_sign_in/test/google_sign_in_test.dart b/packages/google_sign_in/test/google_sign_in_test.dart index dab480a4cce4..108edf9c892b 100755 --- a/packages/google_sign_in/test/google_sign_in_test.dart +++ b/packages/google_sign_in/test/google_sign_in_test.dart @@ -45,7 +45,11 @@ void main() { responses = Map.from(kDefaultResponses); channel.setMockMethodCallHandler((MethodCall methodCall) { log.add(methodCall); - return Future.value(responses[methodCall.method]); + final dynamic response = responses[methodCall.method]; + if (response != null && response is Exception) { + return Future.error('$response'); + } + return Future.value(response); }); googleSignIn = GoogleSignIn(); log.clear(); @@ -142,6 +146,27 @@ void main() { ]); }); + test('signIn works even if a previous call throws error in other zone', + () async { + responses['signInSilently'] = Exception('Not a user'); + await runZoned(() async { + expect(await googleSignIn.signInSilently(), isNull); + }, onError: (dynamic e, dynamic st) {}); + expect(await googleSignIn.signIn(), isNotNull); + expect( + log, + [ + isMethodCall('init', arguments: { + 'signInOption': 'SignInOption.standard', + 'scopes': [], + 'hostedDomain': null, + }), + isMethodCall('signInSilently', arguments: null), + isMethodCall('signIn', arguments: null), + ], + ); + }); + test('concurrent calls of the same method trigger sign in once', () async { final List> futures = >[ @@ -170,7 +195,7 @@ void main() { }); test('can sign in after previously failed attempt', () async { - responses['signInSilently'] = {'error': 'Not a user'}; + responses['signInSilently'] = Exception('Not a user'); expect(await googleSignIn.signInSilently(), isNull); expect(await googleSignIn.signIn(), isNotNull); expect( diff --git a/packages/url_launcher/CHANGELOG.md b/packages/url_launcher/CHANGELOG.md index db2ace15b1e9..0ffbf290a5f7 100644 --- a/packages/url_launcher/CHANGELOG.md +++ b/packages/url_launcher/CHANGELOG.md @@ -1,3 +1,8 @@ + +## 5.1.3 + +* Always launch url from the top most UIViewController in iOS. + ## 5.1.2 * Update AGP and gradle. diff --git a/packages/url_launcher/ios/Classes/UrlLauncherPlugin.m b/packages/url_launcher/ios/Classes/UrlLauncherPlugin.m index 56681dcd1ee3..b21829867a7e 100644 --- a/packages/url_launcher/ios/Classes/UrlLauncherPlugin.m +++ b/packages/url_launcher/ios/Classes/UrlLauncherPlugin.m @@ -61,33 +61,16 @@ @interface FLTUrlLauncherPlugin () @end -@interface FLTUrlLauncherPlugin () - -@property(strong, nonatomic) UIViewController *viewController; - -@end - @implementation FLTUrlLauncherPlugin + (void)registerWithRegistrar:(NSObject *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/url_launcher" binaryMessenger:registrar.messenger]; - UIViewController *viewController = - [UIApplication sharedApplication].delegate.window.rootViewController; - FLTUrlLauncherPlugin *plugin = - [[FLTUrlLauncherPlugin alloc] initWithViewController:viewController]; + FLTUrlLauncherPlugin *plugin = [[FLTUrlLauncherPlugin alloc] init]; [registrar addMethodCallDelegate:plugin channel:channel]; } -- (instancetype)initWithViewController:(UIViewController *)viewController { - self = [super init]; - if (self) { - self.viewController = viewController; - } - return self; -} - - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { NSString *url = call.arguments[@"url"]; if ([@"canLaunch" isEqualToString:call.method]) { @@ -153,9 +136,9 @@ - (void)launchURLInVC:(NSString *)urlString result:(FlutterResult)result API_AVA self.currentSession.didFinish = ^(void) { weakSelf.currentSession = nil; }; - [self.viewController presentViewController:self.currentSession.safari - animated:YES - completion:nil]; + [self.topViewController presentViewController:self.currentSession.safari + animated:YES + completion:nil]; } - (void)closeWebViewWithResult:(FlutterResult)result API_AVAILABLE(ios(9.0)) { @@ -165,4 +148,36 @@ - (void)closeWebViewWithResult:(FlutterResult)result API_AVAILABLE(ios(9.0)) { result(nil); } +- (UIViewController *)topViewController { + return [self topViewControllerFromViewController:[UIApplication sharedApplication] + .keyWindow.rootViewController]; +} + +/** + * This method recursively iterate through the view hierarchy + * to return the top most view controller. + * + * It supports the following scenarios: + * + * - The view controller is presenting another view. + * - The view controller is a UINavigationController. + * - The view controller is a UITabBarController. + * + * @return The top most view controller. + */ +- (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController { + if ([viewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *navigationController = (UINavigationController *)viewController; + return [self + topViewControllerFromViewController:[navigationController.viewControllers lastObject]]; + } + if ([viewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tabController = (UITabBarController *)viewController; + return [self topViewControllerFromViewController:tabController.selectedViewController]; + } + if (viewController.presentedViewController) { + return [self topViewControllerFromViewController:viewController.presentedViewController]; + } + return viewController; +} @end diff --git a/packages/url_launcher/pubspec.yaml b/packages/url_launcher/pubspec.yaml index f7b77db58818..34343639e9f2 100644 --- a/packages/url_launcher/pubspec.yaml +++ b/packages/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 -version: 5.1.2 +version: 5.1.3 flutter: plugin: diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 4419c2f2f03a..98e312afd3a3 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.14+1 + +* Allow underscores anywhere for Javascript Channel name. + ## 0.3.14 * Added a getTitle getter to WebViewController. diff --git a/packages/webview_flutter/lib/webview_flutter.dart b/packages/webview_flutter/lib/webview_flutter.dart index dec62b700122..cd5ca46701d7 100644 --- a/packages/webview_flutter/lib/webview_flutter.dart +++ b/packages/webview_flutter/lib/webview_flutter.dart @@ -91,7 +91,7 @@ enum AutoMediaPlaybackPolicy { always_allow, } -final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9]*\$'); +final RegExp _validChannelNames = RegExp('^[a-zA-Z_][a-zA-Z0-9_]*\$'); /// A named channel for receiving messaged from JavaScript code running inside a web view. class JavascriptChannel { diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index 69f45118de0a..e5f39f14da87 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.14 +version: 0.3.14+1 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter diff --git a/packages/webview_flutter/test/webview_flutter_test.dart b/packages/webview_flutter/test/webview_flutter_test.dart index d3f289018073..5184ddd13de3 100644 --- a/packages/webview_flutter/test/webview_flutter_test.dart +++ b/packages/webview_flutter/test/webview_flutter_test.dart @@ -454,6 +454,7 @@ void main() { final JavascriptMessageHandler noOp = (JavascriptMessage msg) {}; JavascriptChannel(name: 'Tts1', onMessageReceived: noOp); JavascriptChannel(name: '_Alarm', onMessageReceived: noOp); + JavascriptChannel(name: 'foo_bar_', onMessageReceived: noOp); VoidCallback createChannel(String name) { return () {